mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-05-01 15:14:29 +02:00
Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay into landscape
This commit is contained in:
commit
561d9ae987
@ -49,9 +49,23 @@ We encourage developers to write their own plugins. Please refer to the "Getting
|
|||||||
|
|
||||||
## Contributing to Core
|
## Contributing to Core
|
||||||
|
|
||||||
**We are currently not accepting contributions to the core.**
|
|
||||||
|
|
||||||
The core is currently licensed under the FUTO Temporary License (FTL). The licensing and ownership of contributions to the core are complex topics that we are still working on. We'll update these guidelines when we have more clarity.
|
### License
|
||||||
|
|
||||||
|
The core is currently licensed under the [Source First License 1.1](./LICENSE.md). All contributors have to sign FUTO Individual Contributor License Agreement before contributions can be accepted. You can read more about it at [https://cla.futo.org/](https://cla.futo.org/).
|
||||||
|
|
||||||
|
### How to Contribute
|
||||||
|
|
||||||
|
1. Fork the core repository.
|
||||||
|
2. Clone your fork.
|
||||||
|
3. Make your changes.
|
||||||
|
4. Commit and push your changes.
|
||||||
|
5. Open a pull request.
|
||||||
|
|
||||||
|
### Guidelines
|
||||||
|
|
||||||
|
- Ensure your code adheres to the existing style.
|
||||||
|
- Include documentation and unit tests (where applicable).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -144,7 +144,6 @@ class Settings : FragmentedStorageFileJson() {
|
|||||||
fun import() {
|
fun import() {
|
||||||
val act = SettingsActivity.getActivity() ?: return;
|
val act = SettingsActivity.getActivity() ?: return;
|
||||||
val intent = MainActivity.getImportOptionsIntent(act);
|
val intent = MainActivity.getImportOptionsIntent(act);
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
|
||||||
act.startActivity(intent);
|
act.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -906,7 +905,7 @@ class Settings : FragmentedStorageFileJson() {
|
|||||||
var enabled: Boolean = true;
|
var enabled: Boolean = true;
|
||||||
|
|
||||||
@FormField(R.string.broadcast, FieldForm.TOGGLE, R.string.broadcast_description, 1)
|
@FormField(R.string.broadcast, FieldForm.TOGGLE, R.string.broadcast_description, 1)
|
||||||
var broadcast: Boolean = true;
|
var broadcast: Boolean = false;
|
||||||
|
|
||||||
@FormField(R.string.connect_discovered, FieldForm.TOGGLE, R.string.connect_discovered_description, 2)
|
@FormField(R.string.connect_discovered, FieldForm.TOGGLE, R.string.connect_discovered_description, 2)
|
||||||
var connectDiscovered: Boolean = true;
|
var connectDiscovered: Boolean = true;
|
||||||
|
@ -6,6 +6,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.text.Layout
|
||||||
import android.text.method.ScrollingMovementMethod
|
import android.text.method.ScrollingMovementMethod
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
@ -198,7 +199,6 @@ class UIDialogs {
|
|||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun showDialog(context: Context, icon: Int, text: String, textDetails: String? = null, code: String? = null, defaultCloseAction: Int, vararg actions: Action) {
|
fun showDialog(context: Context, icon: Int, text: String, textDetails: String? = null, code: String? = null, defaultCloseAction: Int, vararg actions: Action) {
|
||||||
val builder = AlertDialog.Builder(context);
|
val builder = AlertDialog.Builder(context);
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.dialog_multi_button, null);
|
val view = LayoutInflater.from(context).inflate(R.layout.dialog_multi_button, null);
|
||||||
@ -214,18 +214,20 @@ class UIDialogs {
|
|||||||
this.text = text;
|
this.text = text;
|
||||||
};
|
};
|
||||||
view.findViewById<TextView>(R.id.dialog_text_details).apply {
|
view.findViewById<TextView>(R.id.dialog_text_details).apply {
|
||||||
if(textDetails == null)
|
if (textDetails == null)
|
||||||
this.visibility = View.GONE;
|
this.visibility = View.GONE;
|
||||||
else
|
else {
|
||||||
this.text = textDetails;
|
this.text = textDetails;
|
||||||
|
this.textAlignment = View.TEXT_ALIGNMENT_VIEW_START
|
||||||
|
}
|
||||||
};
|
};
|
||||||
view.findViewById<TextView>(R.id.dialog_text_code).apply {
|
view.findViewById<TextView>(R.id.dialog_text_code).apply {
|
||||||
if(code == null)
|
if (code == null) this.visibility = View.GONE;
|
||||||
this.visibility = View.GONE;
|
|
||||||
else {
|
else {
|
||||||
this.text = code;
|
this.text = code;
|
||||||
this.movementMethod = ScrollingMovementMethod.getInstance();
|
this.movementMethod = ScrollingMovementMethod.getInstance();
|
||||||
this.visibility = View.VISIBLE;
|
this.visibility = View.VISIBLE;
|
||||||
|
this.textAlignment = View.TEXT_ALIGNMENT_VIEW_START
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
view.findViewById<LinearLayout>(R.id.dialog_buttons).apply {
|
view.findViewById<LinearLayout>(R.id.dialog_buttons).apply {
|
||||||
|
@ -32,6 +32,7 @@ import com.futo.platformplayer.BuildConfig
|
|||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.Settings
|
import com.futo.platformplayer.Settings
|
||||||
import com.futo.platformplayer.UIDialogs
|
import com.futo.platformplayer.UIDialogs
|
||||||
|
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||||
import com.futo.platformplayer.casting.StateCasting
|
import com.futo.platformplayer.casting.StateCasting
|
||||||
import com.futo.platformplayer.constructs.Event1
|
import com.futo.platformplayer.constructs.Event1
|
||||||
import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment
|
import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment
|
||||||
@ -80,6 +81,7 @@ import com.futo.platformplayer.states.StatePlayer
|
|||||||
import com.futo.platformplayer.states.StatePlaylists
|
import com.futo.platformplayer.states.StatePlaylists
|
||||||
import com.futo.platformplayer.states.StateSubscriptions
|
import com.futo.platformplayer.states.StateSubscriptions
|
||||||
import com.futo.platformplayer.stores.FragmentedStorage
|
import com.futo.platformplayer.stores.FragmentedStorage
|
||||||
|
import com.futo.platformplayer.stores.StringStorage
|
||||||
import com.futo.platformplayer.stores.SubscriptionStorage
|
import com.futo.platformplayer.stores.SubscriptionStorage
|
||||||
import com.futo.platformplayer.stores.v2.ManagedStore
|
import com.futo.platformplayer.stores.v2.ManagedStore
|
||||||
import com.futo.platformplayer.views.ToastView
|
import com.futo.platformplayer.views.ToastView
|
||||||
@ -87,11 +89,14 @@ import com.futo.polycentric.core.ApiMethods
|
|||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import com.google.zxing.integration.android.IntentIntegrator
|
import com.google.zxing.integration.android.IntentIntegrator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
@ -101,7 +106,6 @@ import java.util.LinkedList
|
|||||||
import java.util.Queue
|
import java.util.Queue
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity, IWithResultLauncher {
|
class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||||
|
|
||||||
//TODO: Move to dimensions
|
//TODO: Move to dimensions
|
||||||
@ -109,7 +113,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
private val HEIGHT_VIDEO_MINIMIZED_DP = 60f;
|
private val HEIGHT_VIDEO_MINIMIZED_DP = 60f;
|
||||||
|
|
||||||
//Containers
|
//Containers
|
||||||
lateinit var rootView : MotionLayout;
|
lateinit var rootView: MotionLayout;
|
||||||
|
|
||||||
private lateinit var _overlayContainer: FrameLayout;
|
private lateinit var _overlayContainer: FrameLayout;
|
||||||
private lateinit var _toastView: ToastView;
|
private lateinit var _toastView: ToastView;
|
||||||
@ -166,11 +170,11 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
lateinit var _fragVideoDetail: VideoDetailFragment;
|
lateinit var _fragVideoDetail: VideoDetailFragment;
|
||||||
|
|
||||||
//State
|
//State
|
||||||
private val _queue : Queue<Pair<MainFragment, Any?>> = LinkedList();
|
private val _queue: Queue<Pair<MainFragment, Any?>> = LinkedList();
|
||||||
lateinit var fragCurrent : MainFragment private set;
|
lateinit var fragCurrent: MainFragment private set;
|
||||||
private var _parameterCurrent: Any? = null;
|
private var _parameterCurrent: Any? = null;
|
||||||
|
|
||||||
var fragBeforeOverlay : MainFragment? = null; private set;
|
var fragBeforeOverlay: MainFragment? = null; private set;
|
||||||
|
|
||||||
val onNavigated = Event1<MainFragment>();
|
val onNavigated = Event1<MainFragment>();
|
||||||
|
|
||||||
@ -216,15 +220,15 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
Logger.e("Application", "Uncaught", excp);
|
Logger.e("Application", "Uncaught", excp);
|
||||||
|
|
||||||
//Resolve invocation chains
|
//Resolve invocation chains
|
||||||
while(excp is InvocationTargetException || excp is java.lang.RuntimeException) {
|
while (excp is InvocationTargetException || excp is java.lang.RuntimeException) {
|
||||||
val before = excp;
|
val before = excp;
|
||||||
|
|
||||||
if(excp is InvocationTargetException)
|
if (excp is InvocationTargetException)
|
||||||
excp = excp.targetException ?: excp.cause ?: excp;
|
excp = excp.targetException ?: excp.cause ?: excp;
|
||||||
else if(excp is java.lang.RuntimeException)
|
else if (excp is java.lang.RuntimeException)
|
||||||
excp = excp.cause ?: excp;
|
excp = excp.cause ?: excp;
|
||||||
|
|
||||||
if(excp == before)
|
if (excp == before)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
writer.write((excp.message ?: "Empty error") + "\n\n");
|
writer.write((excp.message ?: "Empty error") + "\n\n");
|
||||||
@ -255,7 +259,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
setNavigationBarColorAndIcons();
|
setNavigationBarColorAndIcons();
|
||||||
if (Settings.instance.playback.allowVideoToGoUnderCutout)
|
if (Settings.instance.playback.allowVideoToGoUnderCutout)
|
||||||
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
window.attributes.layoutInDisplayCutoutMode =
|
||||||
|
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
StatePlatform.instance.updateAvailableClients(this@MainActivity);
|
StatePlatform.instance.updateAvailableClients(this@MainActivity);
|
||||||
@ -329,10 +334,12 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
updateSegmentPaddings();
|
updateSegmentPaddings();
|
||||||
};
|
};
|
||||||
_fragVideoDetail.onTransitioning.subscribe {
|
_fragVideoDetail.onTransitioning.subscribe {
|
||||||
if(it || _fragVideoDetail.state != VideoDetailFragment.State.MINIMIZED)
|
if (it || _fragVideoDetail.state != VideoDetailFragment.State.MINIMIZED)
|
||||||
_fragContainerOverlay.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15f, resources.displayMetrics);
|
_fragContainerOverlay.elevation =
|
||||||
|
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15f, resources.displayMetrics);
|
||||||
else
|
else
|
||||||
_fragContainerOverlay.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics);
|
_fragContainerOverlay.elevation =
|
||||||
|
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
_fragVideoDetail.onCloseEvent.subscribe {
|
_fragVideoDetail.onCloseEvent.subscribe {
|
||||||
@ -349,40 +356,39 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
_buttonIncognito.alpha = 0f;
|
_buttonIncognito.alpha = 0f;
|
||||||
StateApp.instance.privateModeChanged.subscribe {
|
StateApp.instance.privateModeChanged.subscribe {
|
||||||
//Messing with visibility causes some issues with layout ordering?
|
//Messing with visibility causes some issues with layout ordering?
|
||||||
if(it) {
|
if (it) {
|
||||||
_buttonIncognito.elevation = 99f;
|
_buttonIncognito.elevation = 99f;
|
||||||
_buttonIncognito.alpha = 1f;
|
_buttonIncognito.alpha = 1f;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
_buttonIncognito.elevation = -99f;
|
_buttonIncognito.elevation = -99f;
|
||||||
_buttonIncognito.alpha = 0f;
|
_buttonIncognito.alpha = 0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_buttonIncognito.setOnClickListener {
|
_buttonIncognito.setOnClickListener {
|
||||||
if(!StateApp.instance.privateMode)
|
if (!StateApp.instance.privateMode)
|
||||||
return@setOnClickListener;
|
return@setOnClickListener;
|
||||||
UIDialogs.showDialog(this, R.drawable.ic_disabled_visible_purple, "Disable Privacy Mode",
|
UIDialogs.showDialog(
|
||||||
|
this, R.drawable.ic_disabled_visible_purple, "Disable Privacy Mode",
|
||||||
"Do you want to disable privacy mode? New videos will be tracked again.", null, 0,
|
"Do you want to disable privacy mode? New videos will be tracked again.", null, 0,
|
||||||
UIDialogs.Action("Cancel", {
|
UIDialogs.Action("Cancel", {
|
||||||
StateApp.instance.setPrivacyMode(true);
|
StateApp.instance.setPrivacyMode(true);
|
||||||
}, UIDialogs.ActionStyle.NONE),
|
}, UIDialogs.ActionStyle.NONE),
|
||||||
UIDialogs.Action("Disable", {
|
UIDialogs.Action("Disable", {
|
||||||
StateApp.instance.setPrivacyMode(false);
|
StateApp.instance.setPrivacyMode(false);
|
||||||
}, UIDialogs.ActionStyle.DANGEROUS));
|
}, UIDialogs.ActionStyle.DANGEROUS)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
_fragVideoDetail.onFullscreenChanged.subscribe {
|
_fragVideoDetail.onFullscreenChanged.subscribe {
|
||||||
Logger.i(TAG, "onFullscreenChanged ${it}");
|
Logger.i(TAG, "onFullscreenChanged ${it}");
|
||||||
|
|
||||||
if(it) {
|
if (it) {
|
||||||
_buttonIncognito.elevation = -99f;
|
_buttonIncognito.elevation = -99f;
|
||||||
_buttonIncognito.alpha = 0f;
|
_buttonIncognito.alpha = 0f;
|
||||||
}
|
} else {
|
||||||
else {
|
if (StateApp.instance.privateMode) {
|
||||||
if(StateApp.instance.privateMode) {
|
|
||||||
_buttonIncognito.elevation = 99f;
|
_buttonIncognito.elevation = 99f;
|
||||||
_buttonIncognito.alpha = 1f;
|
_buttonIncognito.alpha = 1f;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
_buttonIncognito.elevation = -99f;
|
_buttonIncognito.elevation = -99f;
|
||||||
_buttonIncognito.alpha = 0f;
|
_buttonIncognito.alpha = 0f;
|
||||||
}
|
}
|
||||||
@ -395,7 +401,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
return@subscribe;
|
return@subscribe;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_fragVideoDetail.state == VideoDetailFragment.State.CLOSED) {
|
if (_fragVideoDetail.state == VideoDetailFragment.State.CLOSED) {
|
||||||
if (fragCurrent !is VideoDetailFragment) {
|
if (fragCurrent !is VideoDetailFragment) {
|
||||||
val toPlay = StatePlayer.instance.getCurrentQueueItem();
|
val toPlay = StatePlayer.instance.getCurrentQueueItem();
|
||||||
navigate(_fragVideoDetail, toPlay);
|
navigate(_fragVideoDetail, toPlay);
|
||||||
@ -447,7 +453,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
fragCurrent = _fragMainHome;
|
fragCurrent = _fragMainHome;
|
||||||
|
|
||||||
val defaultTab = Settings.instance.tabs.mapNotNull {
|
val defaultTab = Settings.instance.tabs.mapNotNull {
|
||||||
val buttonDefinition = MenuBottomBarFragment.buttonDefinitions.firstOrNull { bd -> it.id == bd.id };
|
val buttonDefinition =
|
||||||
|
MenuBottomBarFragment.buttonDefinitions.firstOrNull { bd -> it.id == bd.id };
|
||||||
if (buttonDefinition == null) {
|
if (buttonDefinition == null) {
|
||||||
return@mapNotNull null;
|
return@mapNotNull null;
|
||||||
} else {
|
} else {
|
||||||
@ -506,7 +513,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
|
|
||||||
//startActivity(Intent(this, TestActivity::class.java));
|
//startActivity(Intent(this, TestActivity::class.java));
|
||||||
|
|
||||||
val sharedPreferences = getSharedPreferences("GrayjayFirstBoot", Context.MODE_PRIVATE)
|
val sharedPreferences =
|
||||||
|
getSharedPreferences("GrayjayFirstBoot", Context.MODE_PRIVATE)
|
||||||
val isFirstBoot = sharedPreferences.getBoolean("IsFirstBoot", true)
|
val isFirstBoot = sharedPreferences.getBoolean("IsFirstBoot", true)
|
||||||
if (isFirstBoot) {
|
if (isFirstBoot) {
|
||||||
UIDialogs.showConfirmationDialog(this, getString(R.string.do_you_want_to_see_the_tutorials_you_can_find_them_at_any_time_through_the_more_button), {
|
UIDialogs.showConfirmationDialog(this, getString(R.string.do_you_want_to_see_the_tutorials_you_can_find_them_at_any_time_through_the_more_button), {
|
||||||
@ -518,6 +526,64 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
|
|
||||||
_fragVideoDetail.detectWindowSize()
|
_fragVideoDetail.detectWindowSize()
|
||||||
_fragVideoDetail.updateOrientation()
|
_fragVideoDetail.updateOrientation()
|
||||||
|
|
||||||
|
val submissionStatus = FragmentedStorage.get<StringStorage>("subscriptionSubmissionStatus")
|
||||||
|
|
||||||
|
val numSubscriptions = StateSubscriptions.instance.getSubscriptionCount()
|
||||||
|
|
||||||
|
val subscriptionsThreshold = 20
|
||||||
|
|
||||||
|
if (
|
||||||
|
submissionStatus.value == ""
|
||||||
|
&& StateApp.instance.getCurrentNetworkState() != StateApp.NetworkState.DISCONNECTED
|
||||||
|
&& numSubscriptions >= subscriptionsThreshold
|
||||||
|
) {
|
||||||
|
|
||||||
|
UIDialogs.showDialog(
|
||||||
|
this,
|
||||||
|
R.drawable.ic_internet,
|
||||||
|
getString(R.string.contribute_personal_subscriptions_list),
|
||||||
|
getString(R.string.contribute_personal_subscriptions_list_description),
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
UIDialogs.Action("Cancel", {
|
||||||
|
submissionStatus.setAndSave("dismissed")
|
||||||
|
}, UIDialogs.ActionStyle.NONE),
|
||||||
|
UIDialogs.Action("Upload", {
|
||||||
|
submissionStatus.setAndSave("submitted")
|
||||||
|
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
@Serializable
|
||||||
|
data class CreatorInfo(val pluginId: String, val url: String)
|
||||||
|
|
||||||
|
val subscriptions =
|
||||||
|
StateSubscriptions.instance.getSubscriptions().map { original ->
|
||||||
|
CreatorInfo(
|
||||||
|
pluginId = original.channel.id.pluginId ?: "",
|
||||||
|
url = original.channel.url
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val json = Json.encodeToString(subscriptions)
|
||||||
|
|
||||||
|
val url = "https://data.grayjay.app/donate-subscription-list"
|
||||||
|
val client = ManagedHttpClient();
|
||||||
|
val headers = hashMapOf(
|
||||||
|
"Content-Type" to "application/json"
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
val response = client.post(url, json, headers)
|
||||||
|
// if it failed retry one time
|
||||||
|
if (!response.isOk) {
|
||||||
|
client.post(url, json, headers)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.i(TAG, "Failed to submit subscription list.", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, UIDialogs.ActionStyle.PRIMARY)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -582,39 +648,45 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleIntent(intent: Intent?) {
|
private fun handleIntent(intent: Intent?) {
|
||||||
if(intent == null)
|
if (intent == null)
|
||||||
return;
|
return;
|
||||||
Logger.i(TAG, "handleIntent started by " + intent.action);
|
Logger.i(TAG, "handleIntent started by " + intent.action);
|
||||||
|
|
||||||
|
|
||||||
var targetData: String? = null;
|
var targetData: String? = null;
|
||||||
|
|
||||||
when(intent.action) {
|
when (intent.action) {
|
||||||
Intent.ACTION_SEND -> {
|
Intent.ACTION_SEND -> {
|
||||||
targetData = intent.getStringExtra(Intent.EXTRA_STREAM) ?: intent.getStringExtra(Intent.EXTRA_TEXT);
|
targetData = intent.getStringExtra(Intent.EXTRA_STREAM)
|
||||||
|
?: intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||||
Logger.i(TAG, "Share Received: " + targetData);
|
Logger.i(TAG, "Share Received: " + targetData);
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent.ACTION_VIEW -> {
|
Intent.ACTION_VIEW -> {
|
||||||
targetData = intent.dataString
|
targetData = intent.dataString
|
||||||
|
|
||||||
if(!targetData.isNullOrEmpty()) {
|
if (!targetData.isNullOrEmpty()) {
|
||||||
Logger.i(TAG, "View Received: " + targetData);
|
Logger.i(TAG, "View Received: " + targetData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"VIDEO" -> {
|
"VIDEO" -> {
|
||||||
val url = intent.getStringExtra("VIDEO");
|
val url = intent.getStringExtra("VIDEO");
|
||||||
navigate(_fragVideoDetail, url);
|
navigate(_fragVideoDetail, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
"IMPORT_OPTIONS" -> {
|
"IMPORT_OPTIONS" -> {
|
||||||
UIDialogs.showImportOptionsDialog(this);
|
UIDialogs.showImportOptionsDialog(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
"ACTION" -> {
|
"ACTION" -> {
|
||||||
val action = intent.getStringExtra("ACTION");
|
val action = intent.getStringExtra("ACTION");
|
||||||
StateDeveloper.instance.testState = "TestPlayback";
|
StateDeveloper.instance.testState = "TestPlayback";
|
||||||
StateDeveloper.instance.testPlayback();
|
StateDeveloper.instance.testPlayback();
|
||||||
}
|
}
|
||||||
|
|
||||||
"TAB" -> {
|
"TAB" -> {
|
||||||
when(intent.getStringExtra("TAB")){
|
when (intent.getStringExtra("TAB")) {
|
||||||
"Sources" -> {
|
"Sources" -> {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
StatePlatform.instance.updateAvailableClients(this@MainActivity, true) //Ideally this is not needed..
|
StatePlatform.instance.updateAvailableClients(this@MainActivity, true) //Ideally this is not needed..
|
||||||
@ -625,7 +697,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
navigate(_fragBrowser, BrowserFragment.NavigateOptions("https://plugins.grayjay.app/phone.html", mapOf(
|
navigate(_fragBrowser, BrowserFragment.NavigateOptions("https://plugins.grayjay.app/phone.html", mapOf(
|
||||||
Pair("grayjay") { req ->
|
Pair("grayjay") { req ->
|
||||||
StateApp.instance.contextOrNull?.let {
|
StateApp.instance.contextOrNull?.let {
|
||||||
if(it is MainActivity) {
|
if (it is MainActivity) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
it.handleUrlAll(req.url.toString());
|
it.handleUrlAll(req.url.toString());
|
||||||
}
|
}
|
||||||
@ -644,8 +716,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
handleUrlAll(targetData)
|
handleUrlAll(targetData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (ex: Throwable) {
|
||||||
catch(ex: Throwable) {
|
|
||||||
UIDialogs.showGeneralErrorDialog(this, getString(R.string.failed_to_handle_file), ex);
|
UIDialogs.showGeneralErrorDialog(this, getString(R.string.failed_to_handle_file), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -654,35 +725,31 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
val uri = Uri.parse(url)
|
val uri = Uri.parse(url)
|
||||||
when (uri.scheme) {
|
when (uri.scheme) {
|
||||||
"grayjay" -> {
|
"grayjay" -> {
|
||||||
if(url.startsWith("grayjay://license/")) {
|
if (url.startsWith("grayjay://license/")) {
|
||||||
if(StatePayment.instance.setPaymentLicenseUrl(url))
|
if (StatePayment.instance.setPaymentLicenseUrl(url)) {
|
||||||
{
|
|
||||||
UIDialogs.showDialogOk(this, R.drawable.ic_check, getString(R.string.your_license_key_has_been_set_an_app_restart_might_be_required));
|
UIDialogs.showDialogOk(this, R.drawable.ic_check, getString(R.string.your_license_key_has_been_set_an_app_restart_might_be_required));
|
||||||
|
|
||||||
if(fragCurrent is BuyFragment)
|
if (fragCurrent is BuyFragment)
|
||||||
closeSegment(fragCurrent);
|
closeSegment(fragCurrent);
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
UIDialogs.toast(getString(R.string.invalid_license_format));
|
UIDialogs.toast(getString(R.string.invalid_license_format));
|
||||||
|
|
||||||
}
|
} else if (url.startsWith("grayjay://plugin/")) {
|
||||||
else if(url.startsWith("grayjay://plugin/")) {
|
|
||||||
val intent = Intent(this, AddSourceActivity::class.java).apply {
|
val intent = Intent(this, AddSourceActivity::class.java).apply {
|
||||||
data = Uri.parse(url.substring("grayjay://plugin/".length));
|
data = Uri.parse(url.substring("grayjay://plugin/".length));
|
||||||
};
|
};
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
} else if (url.startsWith("grayjay://video/")) {
|
||||||
else if(url.startsWith("grayjay://video/")) {
|
|
||||||
val videoUrl = url.substring("grayjay://video/".length);
|
val videoUrl = url.substring("grayjay://video/".length);
|
||||||
navigate(_fragVideoDetail, videoUrl);
|
navigate(_fragVideoDetail, videoUrl);
|
||||||
}
|
} else if (url.startsWith("grayjay://channel/")) {
|
||||||
else if(url.startsWith("grayjay://channel/")) {
|
|
||||||
val channelUrl = url.substring("grayjay://channel/".length);
|
val channelUrl = url.substring("grayjay://channel/".length);
|
||||||
navigate(_fragMainChannel, channelUrl);
|
navigate(_fragMainChannel, channelUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"content" -> {
|
"content" -> {
|
||||||
if(!handleContent(url, intent.type)) {
|
if (!handleContent(url, intent.type)) {
|
||||||
UIDialogs.showSingleButtonDialog(
|
UIDialogs.showSingleButtonDialog(
|
||||||
this,
|
this,
|
||||||
R.drawable.ic_play,
|
R.drawable.ic_play,
|
||||||
@ -691,8 +758,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
{ });
|
{ });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"file" -> {
|
"file" -> {
|
||||||
if(!handleFile(url)) {
|
if (!handleFile(url)) {
|
||||||
UIDialogs.showSingleButtonDialog(
|
UIDialogs.showSingleButtonDialog(
|
||||||
this,
|
this,
|
||||||
R.drawable.ic_play,
|
R.drawable.ic_play,
|
||||||
@ -701,8 +769,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
{ });
|
{ });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"polycentric" -> {
|
"polycentric" -> {
|
||||||
if(!handlePolycentric(url)) {
|
if (!handlePolycentric(url)) {
|
||||||
UIDialogs.showSingleButtonDialog(
|
UIDialogs.showSingleButtonDialog(
|
||||||
this,
|
this,
|
||||||
R.drawable.ic_play,
|
R.drawable.ic_play,
|
||||||
@ -711,8 +780,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
{ });
|
{ });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"fcast" -> {
|
"fcast" -> {
|
||||||
if(!handleFCast(url)) {
|
if (!handleFCast(url)) {
|
||||||
UIDialogs.showSingleButtonDialog(
|
UIDialogs.showSingleButtonDialog(
|
||||||
this,
|
this,
|
||||||
R.drawable.ic_cast,
|
R.drawable.ic_cast,
|
||||||
@ -721,6 +791,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
{ });
|
{ });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
if (!handleUrl(url)) {
|
if (!handleUrl(url)) {
|
||||||
UIDialogs.showSingleButtonDialog(
|
UIDialogs.showSingleButtonDialog(
|
||||||
@ -742,7 +813,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
if (StatePlatform.instance.hasEnabledVideoClient(url)) {
|
if (StatePlatform.instance.hasEnabledVideoClient(url)) {
|
||||||
Logger.i(TAG, "handleUrl(url=$url) found video client");
|
Logger.i(TAG, "handleUrl(url=$url) found video client");
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
if(position > 0)
|
if (position > 0)
|
||||||
navigate(_fragVideoDetail, UrlVideoWithTime(url, position.toLong(), true));
|
navigate(_fragVideoDetail, UrlVideoWithTime(url, position.toLong(), true));
|
||||||
else
|
else
|
||||||
navigate(_fragVideoDetail, url);
|
navigate(_fragVideoDetail, url);
|
||||||
@ -770,24 +841,25 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
return@withContext false;
|
return@withContext false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleContent(file: String, mime: String? = null): Boolean {
|
fun handleContent(file: String, mime: String? = null): Boolean {
|
||||||
Logger.i(TAG, "handleContent(url=$file)");
|
Logger.i(TAG, "handleContent(url=$file)");
|
||||||
|
|
||||||
val data = readSharedContent(file);
|
val data = readSharedContent(file);
|
||||||
if(file.lowercase().endsWith(".json") || mime == "application/json") {
|
if (file.lowercase().endsWith(".json") || mime == "application/json") {
|
||||||
var recon = String(data);
|
var recon = String(data);
|
||||||
if(!recon.trim().startsWith("["))
|
if (!recon.trim().startsWith("["))
|
||||||
return handleUnknownJson(recon);
|
return handleUnknownJson(recon);
|
||||||
|
|
||||||
var reconLines = Json.decodeFromString<List<String>>(recon);
|
var reconLines = Json.decodeFromString<List<String>>(recon);
|
||||||
val cacheStr = reconLines.find { it.startsWith("__CACHE:") }?.substring("__CACHE:".length);
|
val cacheStr =
|
||||||
|
reconLines.find { it.startsWith("__CACHE:") }?.substring("__CACHE:".length);
|
||||||
reconLines = reconLines.filter { !it.startsWith("__CACHE:") }; //TODO: constant prefix
|
reconLines = reconLines.filter { !it.startsWith("__CACHE:") }; //TODO: constant prefix
|
||||||
var cache: ImportCache? = null;
|
var cache: ImportCache? = null;
|
||||||
try {
|
try {
|
||||||
if(cacheStr != null)
|
if (cacheStr != null)
|
||||||
cache = Json.decodeFromString(cacheStr);
|
cache = Json.decodeFromString(cacheStr);
|
||||||
}
|
} catch (ex: Throwable) {
|
||||||
catch(ex: Throwable) {
|
|
||||||
Logger.e(TAG, "Failed to deserialize cache");
|
Logger.e(TAG, "Failed to deserialize cache");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -796,32 +868,31 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}");
|
Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}");
|
||||||
handleReconstruction(recon, cache);
|
handleReconstruction(recon, cache);
|
||||||
return true;
|
return true;
|
||||||
}
|
} else if (file.lowercase().endsWith(".zip") || mime == "application/zip") {
|
||||||
else if(file.lowercase().endsWith(".zip") || mime == "application/zip") {
|
|
||||||
StateBackup.importZipBytes(this, lifecycleScope, data);
|
StateBackup.importZipBytes(this, lifecycleScope, data);
|
||||||
return true;
|
return true;
|
||||||
}
|
} else if (file.lowercase().endsWith(".txt") || mime == "text/plain") {
|
||||||
else if(file.lowercase().endsWith(".txt") || mime == "text/plain") {
|
|
||||||
return handleUnknownText(String(data));
|
return handleUnknownText(String(data));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleFile(file: String): Boolean {
|
fun handleFile(file: String): Boolean {
|
||||||
Logger.i(TAG, "handleFile(url=$file)");
|
Logger.i(TAG, "handleFile(url=$file)");
|
||||||
if(file.lowercase().endsWith(".json")) {
|
if (file.lowercase().endsWith(".json")) {
|
||||||
var recon = String(readSharedFile(file));
|
var recon = String(readSharedFile(file));
|
||||||
if(!recon.startsWith("["))
|
if (!recon.startsWith("["))
|
||||||
return handleUnknownJson(recon);
|
return handleUnknownJson(recon);
|
||||||
|
|
||||||
var reconLines = Json.decodeFromString<List<String>>(recon);
|
var reconLines = Json.decodeFromString<List<String>>(recon);
|
||||||
val cacheStr = reconLines.find { it.startsWith("__CACHE:") }?.substring("__CACHE:".length);
|
val cacheStr =
|
||||||
|
reconLines.find { it.startsWith("__CACHE:") }?.substring("__CACHE:".length);
|
||||||
reconLines = reconLines.filter { !it.startsWith("__CACHE:") }; //TODO: constant prefix
|
reconLines = reconLines.filter { !it.startsWith("__CACHE:") }; //TODO: constant prefix
|
||||||
var cache: ImportCache? = null;
|
var cache: ImportCache? = null;
|
||||||
try {
|
try {
|
||||||
if(cacheStr != null)
|
if (cacheStr != null)
|
||||||
cache = Json.decodeFromString(cacheStr);
|
cache = Json.decodeFromString(cacheStr);
|
||||||
}
|
} catch (ex: Throwable) {
|
||||||
catch(ex: Throwable) {
|
|
||||||
Logger.e(TAG, "Failed to deserialize cache");
|
Logger.e(TAG, "Failed to deserialize cache");
|
||||||
}
|
}
|
||||||
recon = reconLines.joinToString("\n");
|
recon = reconLines.joinToString("\n");
|
||||||
@ -829,19 +900,18 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}");
|
Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}");
|
||||||
handleReconstruction(recon, cache);
|
handleReconstruction(recon, cache);
|
||||||
return true;
|
return true;
|
||||||
}
|
} else if (file.lowercase().endsWith(".zip")) {
|
||||||
else if(file.lowercase().endsWith(".zip")) {
|
|
||||||
StateBackup.importZipBytes(this, lifecycleScope, readSharedFile(file));
|
StateBackup.importZipBytes(this, lifecycleScope, readSharedFile(file));
|
||||||
return true;
|
return true;
|
||||||
}
|
} else if (file.lowercase().endsWith(".txt")) {
|
||||||
else if(file.lowercase().endsWith(".txt")) {
|
|
||||||
return handleUnknownText(String(readSharedFile(file)));
|
return handleUnknownText(String(readSharedFile(file)));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleReconstruction(recon: String, cache: ImportCache? = null) {
|
fun handleReconstruction(recon: String, cache: ImportCache? = null) {
|
||||||
val type = ManagedStore.getReconstructionIdentifier(recon);
|
val type = ManagedStore.getReconstructionIdentifier(recon);
|
||||||
val store: ManagedStore<*> = when(type) {
|
val store: ManagedStore<*> = when (type) {
|
||||||
"Playlist" -> StatePlaylists.instance.playlistStore
|
"Playlist" -> StatePlaylists.instance.playlistStore
|
||||||
else -> {
|
else -> {
|
||||||
UIDialogs.toast(getString(R.string.unknown_reconstruction_type) + " ${type}", false);
|
UIDialogs.toast(getString(R.string.unknown_reconstruction_type) + " ${type}", false);
|
||||||
@ -849,13 +919,15 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
val name = when(type) {
|
val name = when (type) {
|
||||||
"Playlist" -> recon.split("\n").filter { !it.startsWith(ManagedStore.RECONSTRUCTION_HEADER_OPERATOR) }.firstOrNull() ?: type;
|
"Playlist" -> recon.split("\n")
|
||||||
|
.filter { !it.startsWith(ManagedStore.RECONSTRUCTION_HEADER_OPERATOR) }
|
||||||
|
.firstOrNull() ?: type;
|
||||||
else -> type
|
else -> type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(!type.isNullOrEmpty()) {
|
if (!type.isNullOrEmpty()) {
|
||||||
UIDialogs.showImportDialog(this, store, name, listOf(recon), cache) {
|
UIDialogs.showImportDialog(this, store, name, listOf(recon), cache) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -864,18 +936,18 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
|
|
||||||
fun handleUnknownText(text: String): Boolean {
|
fun handleUnknownText(text: String): Boolean {
|
||||||
try {
|
try {
|
||||||
if(text.startsWith("@/Subscription") || text.startsWith("Subscriptions")) {
|
if (text.startsWith("@/Subscription") || text.startsWith("Subscriptions")) {
|
||||||
val lines = text.split("\n").map { it.trim() }.drop(1).filter { it.isNotEmpty() };
|
val lines = text.split("\n").map { it.trim() }.drop(1).filter { it.isNotEmpty() };
|
||||||
navigate(_fragImportSubscriptions, lines);
|
navigate(_fragImportSubscriptions, lines);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
} catch (ex: Throwable) {
|
||||||
catch(ex: Throwable) {
|
|
||||||
Logger.e(TAG, ex.message, ex);
|
Logger.e(TAG, ex.message, ex);
|
||||||
UIDialogs.showGeneralErrorDialog(this, getString(R.string.failed_to_parse_text_file), ex);
|
UIDialogs.showGeneralErrorDialog(this, getString(R.string.failed_to_parse_text_file), ex);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleUnknownJson(json: String): Boolean {
|
fun handleUnknownJson(json: String): Boolean {
|
||||||
|
|
||||||
val context = this;
|
val context = this;
|
||||||
@ -887,8 +959,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
return false;//throw IllegalArgumentException("Invalid NewPipe json structure found");
|
return false;//throw IllegalArgumentException("Invalid NewPipe json structure found");
|
||||||
|
|
||||||
StateBackup.importNewPipeSubs(this, newPipeSubsParsed);
|
StateBackup.importNewPipeSubs(this, newPipeSubsParsed);
|
||||||
}
|
} catch (ex: Exception) {
|
||||||
catch(ex: Exception) {
|
|
||||||
Logger.e(TAG, ex.message, ex);
|
Logger.e(TAG, ex.message, ex);
|
||||||
UIDialogs.showGeneralErrorDialog(context, getString(R.string.failed_to_parse_newpipe_subscriptions), ex);
|
UIDialogs.showGeneralErrorDialog(context, getString(R.string.failed_to_parse_newpipe_subscriptions), ex);
|
||||||
}
|
}
|
||||||
@ -934,7 +1005,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
|
|
||||||
private fun readSharedFile(filePath: String): ByteArray {
|
private fun readSharedFile(filePath: String): ByteArray {
|
||||||
val dataFile = File(filePath);
|
val dataFile = File(filePath);
|
||||||
if(!dataFile.exists())
|
if (!dataFile.exists())
|
||||||
throw IllegalArgumentException("Opened file does not exist or not permitted");
|
throw IllegalArgumentException("Opened file does not exist or not permitted");
|
||||||
val data = dataFile.readBytes();
|
val data = dataFile.readBytes();
|
||||||
return data;
|
return data;
|
||||||
@ -943,13 +1014,13 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
Logger.i(TAG, "onBackPressed")
|
Logger.i(TAG, "onBackPressed")
|
||||||
|
|
||||||
if(_fragBotBarMenu.onBackPressed())
|
if (_fragBotBarMenu.onBackPressed())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED && _fragVideoDetail.onBackPressed())
|
if (_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED && _fragVideoDetail.onBackPressed())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(!fragCurrent.onBackPressed())
|
if (!fragCurrent.onBackPressed())
|
||||||
closeSegment();
|
closeSegment();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -957,7 +1028,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
super.onUserLeaveHint();
|
super.onUserLeaveHint();
|
||||||
Logger.i(TAG, "onUserLeaveHint")
|
Logger.i(TAG, "onUserLeaveHint")
|
||||||
|
|
||||||
if(_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED || _fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED)
|
if (_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED || _fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED)
|
||||||
_fragVideoDetail.onUserLeaveHint();
|
_fragVideoDetail.onUserLeaveHint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -993,12 +1064,12 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
fun navigate(segment: MainFragment, parameter: Any? = null, withHistory: Boolean = true, isBack: Boolean = false) {
|
fun navigate(segment: MainFragment, parameter: Any? = null, withHistory: Boolean = true, isBack: Boolean = false) {
|
||||||
Logger.i(TAG, "Navigate to $segment (parameter=$parameter, withHistory=$withHistory, isBack=$isBack)")
|
Logger.i(TAG, "Navigate to $segment (parameter=$parameter, withHistory=$withHistory, isBack=$isBack)")
|
||||||
|
|
||||||
if(segment != fragCurrent) {
|
if (segment != fragCurrent) {
|
||||||
|
|
||||||
if(segment is VideoDetailFragment) {
|
if (segment is VideoDetailFragment) {
|
||||||
if(_fragContainerVideoDetail.visibility != View.VISIBLE)
|
if (_fragContainerVideoDetail.visibility != View.VISIBLE)
|
||||||
_fragContainerVideoDetail.visibility = View.VISIBLE;
|
_fragContainerVideoDetail.visibility = View.VISIBLE;
|
||||||
when(segment.state) {
|
when (segment.state) {
|
||||||
VideoDetailFragment.State.MINIMIZED -> segment.maximizeVideoDetail()
|
VideoDetailFragment.State.MINIMIZED -> segment.maximizeVideoDetail()
|
||||||
VideoDetailFragment.State.CLOSED -> segment.maximizeVideoDetail()
|
VideoDetailFragment.State.CLOSED -> segment.maximizeVideoDetail()
|
||||||
else -> {}
|
else -> {}
|
||||||
@ -1007,10 +1078,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fragCurrent.onHide();
|
fragCurrent.onHide();
|
||||||
|
|
||||||
if(segment.isMainView) {
|
if (segment.isMainView) {
|
||||||
var transaction = supportFragmentManager.beginTransaction();
|
var transaction = supportFragmentManager.beginTransaction();
|
||||||
if (segment.topBar != null) {
|
if (segment.topBar != null) {
|
||||||
if (segment.topBar != fragCurrent.topBar) {
|
if (segment.topBar != fragCurrent.topBar) {
|
||||||
@ -1019,8 +1089,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
.replace(R.id.fragment_top_bar, segment.topBar as Fragment);
|
.replace(R.id.fragment_top_bar, segment.topBar as Fragment);
|
||||||
fragCurrent.topBar?.onHide();
|
fragCurrent.topBar?.onHide();
|
||||||
}
|
}
|
||||||
}
|
} else if (fragCurrent.topBar != null)
|
||||||
else if(fragCurrent.topBar != null)
|
|
||||||
transaction.hide(fragCurrent.topBar as Fragment);
|
transaction.hide(fragCurrent.topBar as Fragment);
|
||||||
|
|
||||||
transaction = transaction.replace(R.id.fragment_main, segment);
|
transaction = transaction.replace(R.id.fragment_main, segment);
|
||||||
@ -1028,25 +1097,24 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
if (segment.hasBottomBar) {
|
if (segment.hasBottomBar) {
|
||||||
if (!fragCurrent.hasBottomBar)
|
if (!fragCurrent.hasBottomBar)
|
||||||
transaction = transaction.show(_fragBotBarMenu);
|
transaction = transaction.show(_fragBotBarMenu);
|
||||||
}
|
} else {
|
||||||
else {
|
if (fragCurrent.hasBottomBar)
|
||||||
if(fragCurrent.hasBottomBar)
|
|
||||||
transaction = transaction.hide(_fragBotBarMenu);
|
transaction = transaction.hide(_fragBotBarMenu);
|
||||||
}
|
}
|
||||||
transaction.commitNow();
|
transaction.commitNow();
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if(!segment.hasBottomBar) {
|
if (!segment.hasBottomBar) {
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.hide(_fragBotBarMenu)
|
.hide(_fragBotBarMenu)
|
||||||
.commitNow();
|
.commitNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(fragCurrent.isHistory && withHistory && _queue.lastOrNull() != fragCurrent)
|
if (fragCurrent.isHistory && withHistory && _queue.lastOrNull() != fragCurrent)
|
||||||
_queue.add(Pair(fragCurrent, _parameterCurrent));
|
_queue.add(Pair(fragCurrent, _parameterCurrent));
|
||||||
|
|
||||||
if(segment.isOverlay && !fragCurrent.isOverlay && withHistory)// && fragCurrent.isHistory)
|
if (segment.isOverlay && !fragCurrent.isOverlay && withHistory)// && fragCurrent.isHistory)
|
||||||
fragBeforeOverlay = fragCurrent;
|
fragBeforeOverlay = fragCurrent;
|
||||||
|
|
||||||
|
|
||||||
@ -1064,12 +1132,12 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
* If called with a non-null fragment, it will only close if the current fragment is the provided one
|
* If called with a non-null fragment, it will only close if the current fragment is the provided one
|
||||||
*/
|
*/
|
||||||
fun closeSegment(fragment: MainFragment? = null) {
|
fun closeSegment(fragment: MainFragment? = null) {
|
||||||
if(fragment is VideoDetailFragment) {
|
if (fragment is VideoDetailFragment) {
|
||||||
fragment.onHide();
|
fragment.onHide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if((fragment?.isOverlay ?: false) && fragBeforeOverlay != null) {
|
if ((fragment?.isOverlay ?: false) && fragBeforeOverlay != null) {
|
||||||
navigate(fragBeforeOverlay!!, null, false, true);
|
navigate(fragBeforeOverlay!!, null, false, true);
|
||||||
} else {
|
} else {
|
||||||
val last = _queue.lastOrNull();
|
val last = _queue.lastOrNull();
|
||||||
@ -1091,8 +1159,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
/**
|
/**
|
||||||
* Provides the fragment instance for the provided fragment class
|
* Provides the fragment instance for the provided fragment class
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : Fragment> getFragment() : T {
|
inline fun <reified T : Fragment> getFragment(): T {
|
||||||
return when(T::class) {
|
return when (T::class) {
|
||||||
HomeFragment::class -> _fragMainHome as T;
|
HomeFragment::class -> _fragMainHome as T;
|
||||||
TutorialFragment::class -> _fragMainTutorial as T;
|
TutorialFragment::class -> _fragMainTutorial as T;
|
||||||
ContentSearchResultsFragment::class -> _fragMainVideoSearchResults as T;
|
ContentSearchResultsFragment::class -> _fragMainVideoSearchResults as T;
|
||||||
@ -1129,15 +1197,21 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
|
|
||||||
private fun updateSegmentPaddings() {
|
private fun updateSegmentPaddings() {
|
||||||
var paddingBottom = 0f;
|
var paddingBottom = 0f;
|
||||||
if(fragCurrent.hasBottomBar)
|
if (fragCurrent.hasBottomBar)
|
||||||
paddingBottom += HEIGHT_MENU_DP;
|
paddingBottom += HEIGHT_MENU_DP;
|
||||||
|
|
||||||
_fragContainerOverlay.setPadding(0,0,0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom - HEIGHT_MENU_DP, resources.displayMetrics).toInt());
|
_fragContainerOverlay.setPadding(
|
||||||
|
0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom - HEIGHT_MENU_DP, resources.displayMetrics)
|
||||||
|
.toInt()
|
||||||
|
);
|
||||||
|
|
||||||
if(_fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED)
|
if (_fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED)
|
||||||
paddingBottom += HEIGHT_VIDEO_MINIMIZED_DP;
|
paddingBottom += HEIGHT_VIDEO_MINIMIZED_DP;
|
||||||
|
|
||||||
_fragContainerMain.setPadding(0,0,0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, resources.displayMetrics).toInt());
|
_fragContainerMain.setPadding(
|
||||||
|
0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, resources.displayMetrics)
|
||||||
|
.toInt()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1153,14 +1227,18 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
ContextCompat.checkSelfPermission(this, notifPermission) == PackageManager.PERMISSION_GRANTED -> {
|
ContextCompat.checkSelfPermission(this, notifPermission) == PackageManager.PERMISSION_GRANTED -> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActivityCompat.shouldShowRequestPermissionRationale(this, notifPermission) -> {
|
ActivityCompat.shouldShowRequestPermissionRationale(this, notifPermission) -> {
|
||||||
UIDialogs.showDialog(this, R.drawable.ic_notifications, "Notifications Required",
|
UIDialogs.showDialog(
|
||||||
|
this, R.drawable.ic_notifications, "Notifications Required",
|
||||||
reason, null, 0,
|
reason, null, 0,
|
||||||
UIDialogs.Action("Cancel", {}),
|
UIDialogs.Action("Cancel", {}),
|
||||||
UIDialogs.Action("Enable", {
|
UIDialogs.Action("Enable", {
|
||||||
requestPermissionLauncher.launch(notifPermission);
|
requestPermissionLauncher.launch(notifPermission);
|
||||||
}, UIDialogs.ActionStyle.PRIMARY));
|
}, UIDialogs.ActionStyle.PRIMARY)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
requestPermissionLauncher.launch(notifPermission);
|
requestPermissionLauncher.launch(notifPermission);
|
||||||
}
|
}
|
||||||
@ -1172,15 +1250,16 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
fun showAppToast(toast: ToastView.Toast) {
|
fun showAppToast(toast: ToastView.Toast) {
|
||||||
synchronized(_toastQueue) {
|
synchronized(_toastQueue) {
|
||||||
_toastQueue.add(toast);
|
_toastQueue.add(toast);
|
||||||
if(_toastJob?.isActive != true)
|
if (_toastJob?.isActive != true)
|
||||||
_toastJob = lifecycleScope.launch(Dispatchers.Default) {
|
_toastJob = lifecycleScope.launch(Dispatchers.Default) {
|
||||||
launchAppToastJob();
|
launchAppToastJob();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun launchAppToastJob() {
|
private suspend fun launchAppToastJob() {
|
||||||
Logger.i(TAG, "Starting appToast loop");
|
Logger.i(TAG, "Starting appToast loop");
|
||||||
while(!_toastQueue.isEmpty()) {
|
while (!_toastQueue.isEmpty()) {
|
||||||
val toast = _toastQueue.poll() ?: continue;
|
val toast = _toastQueue.poll() ?: continue;
|
||||||
Logger.i(TAG, "Showing next toast (${toast.msg})");
|
Logger.i(TAG, "Showing next toast (${toast.msg})");
|
||||||
|
|
||||||
@ -1193,7 +1272,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
_toastView.setToastAnimated(toast);
|
_toastView.setToastAnimated(toast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(toast.long)
|
if (toast.long)
|
||||||
delay(5000);
|
delay(5000);
|
||||||
else
|
else
|
||||||
delay(3000);
|
delay(3000);
|
||||||
@ -1207,18 +1286,19 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
|
|
||||||
|
|
||||||
//TODO: Only calls last handler due to missing request codes on ActivityResultLaunchers.
|
//TODO: Only calls last handler due to missing request codes on ActivityResultLaunchers.
|
||||||
private var resultLauncherMap = mutableMapOf<Int, (ActivityResult)->Unit>();
|
private var resultLauncherMap = mutableMapOf<Int, (ActivityResult) -> Unit>();
|
||||||
private var requestCode: Int? = -1;
|
private var requestCode: Int? = -1;
|
||||||
private val resultLauncher: ActivityResultLauncher<Intent> = registerForActivityResult(
|
private val resultLauncher: ActivityResultLauncher<Intent> = registerForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult()) {
|
ActivityResultContracts.StartActivityForResult()
|
||||||
result: ActivityResult ->
|
) { result: ActivityResult ->
|
||||||
val handler = synchronized(resultLauncherMap) {
|
val handler = synchronized(resultLauncherMap) {
|
||||||
resultLauncherMap.remove(requestCode);
|
resultLauncherMap.remove(requestCode);
|
||||||
}
|
}
|
||||||
if(handler != null)
|
if (handler != null)
|
||||||
handler(result);
|
handler(result);
|
||||||
};
|
};
|
||||||
override fun launchForResult(intent: Intent, code: Int, handler: (ActivityResult)->Unit) {
|
|
||||||
|
override fun launchForResult(intent: Intent, code: Int, handler: (ActivityResult) -> Unit) {
|
||||||
synchronized(resultLauncherMap) {
|
synchronized(resultLauncherMap) {
|
||||||
resultLauncherMap[code] = handler;
|
resultLauncherMap[code] = handler;
|
||||||
}
|
}
|
||||||
@ -1229,32 +1309,34 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||||||
companion object {
|
companion object {
|
||||||
private val TAG = "MainActivity"
|
private val TAG = "MainActivity"
|
||||||
|
|
||||||
fun getTabIntent(context: Context, tab: String) : Intent {
|
fun getTabIntent(context: Context, tab: String): Intent {
|
||||||
val sourcesIntent = Intent(context, MainActivity::class.java);
|
val sourcesIntent = Intent(context, MainActivity::class.java);
|
||||||
sourcesIntent.action = "TAB";
|
sourcesIntent.action = "TAB";
|
||||||
sourcesIntent.putExtra("TAB", tab);
|
sourcesIntent.putExtra("TAB", tab);
|
||||||
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
return sourcesIntent;
|
return sourcesIntent;
|
||||||
}
|
}
|
||||||
fun getVideoIntent(context: Context, videoUrl: String) : Intent {
|
|
||||||
|
fun getVideoIntent(context: Context, videoUrl: String): Intent {
|
||||||
val sourcesIntent = Intent(context, MainActivity::class.java);
|
val sourcesIntent = Intent(context, MainActivity::class.java);
|
||||||
sourcesIntent.action = "VIDEO";
|
sourcesIntent.action = "VIDEO";
|
||||||
sourcesIntent.putExtra("VIDEO", videoUrl);
|
sourcesIntent.putExtra("VIDEO", videoUrl);
|
||||||
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
return sourcesIntent;
|
return sourcesIntent;
|
||||||
}
|
}
|
||||||
fun getActionIntent(context: Context, action: String) : Intent {
|
|
||||||
|
fun getActionIntent(context: Context, action: String): Intent {
|
||||||
val sourcesIntent = Intent(context, MainActivity::class.java);
|
val sourcesIntent = Intent(context, MainActivity::class.java);
|
||||||
sourcesIntent.action = "ACTION";
|
sourcesIntent.action = "ACTION";
|
||||||
sourcesIntent.putExtra("ACTION", action);
|
sourcesIntent.putExtra("ACTION", action);
|
||||||
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
return sourcesIntent;
|
return sourcesIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getImportOptionsIntent(context: Context): Intent {
|
fun getImportOptionsIntent(context: Context): Intent {
|
||||||
val sourcesIntent = Intent(context, MainActivity::class.java);
|
val sourcesIntent = Intent(context, MainActivity::class.java);
|
||||||
sourcesIntent.action = "IMPORT_OPTIONS";
|
sourcesIntent.action = "IMPORT_OPTIONS";
|
||||||
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
return sourcesIntent;
|
return sourcesIntent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -933,7 +933,7 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
val device = devices.first();
|
val device = devices.first();
|
||||||
UIDialogs.showConfirmationDialog(context, "Would you like to open\n[${videoToSend.name}]\non ${device.remotePublicKey}" , {
|
UIDialogs.showConfirmationDialog(context, "Would you like to open\n[${videoToSend.name}]\non ${device.remotePublicKey}" , {
|
||||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
device.sendJson(GJSyncOpcodes.sendToDevices, SendToDevicePackage(videoToSend.url, (lastPositionMilliseconds/1000).toInt()));
|
device.sendJsonData(GJSyncOpcodes.sendToDevices, SendToDevicePackage(videoToSend.url, (lastPositionMilliseconds/1000).toInt()));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1362,11 +1362,9 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
me._playbackTracker = null;
|
me._playbackTracker = null;
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
Logger.e(TAG, "Playback tracker failed", ex);
|
Logger.e(TAG, "Playback tracker failed", ex);
|
||||||
if (me.video?.isLive == true) withContext(Dispatchers.Main) {
|
|
||||||
UIDialogs.toast(
|
if(me.video?.isLive == true || ex.message?.contains("Unable to resolve host") == true) withContext(Dispatchers.Main) {
|
||||||
context,
|
UIDialogs.toast(context, context.getString(R.string.failed_to_get_playback_tracker));
|
||||||
context.getString(R.string.failed_to_get_playback_tracker)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
else withContext(Dispatchers.Main) {
|
else withContext(Dispatchers.Main) {
|
||||||
UIDialogs.showGeneralErrorDialog(
|
UIDialogs.showGeneralErrorDialog(
|
||||||
@ -1759,7 +1757,7 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
});
|
});
|
||||||
else
|
else
|
||||||
_player.setArtwork(null);
|
_player.setArtwork(null);
|
||||||
_player.setSource(videoSource, audioSource, _playWhenReady, false);
|
_player.setSource(videoSource, audioSource, _playWhenReady, false, resume = resumePositionMs > 0);
|
||||||
if(subtitleSource != null)
|
if(subtitleSource != null)
|
||||||
_player.swapSubtitles(fragment.lifecycleScope, subtitleSource);
|
_player.swapSubtitles(fragment.lifecycleScope, subtitleSource);
|
||||||
_player.seekTo(resumePositionMs);
|
_player.seekTo(resumePositionMs);
|
||||||
@ -2915,6 +2913,7 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
.exception<Throwable> {
|
.exception<Throwable> {
|
||||||
Logger.w(ChannelFragment.TAG, "Failed to load video.", it);
|
Logger.w(ChannelFragment.TAG, "Failed to load video.", it);
|
||||||
|
|
||||||
|
if(!(it.message?.contains("Unable to resolve host") ?: false && nextVideo())){
|
||||||
handleErrorOrCall {
|
handleErrorOrCall {
|
||||||
_retryCount = 0;
|
_retryCount = 0;
|
||||||
_retryJob?.cancel();
|
_retryJob?.cancel();
|
||||||
@ -2923,6 +2922,7 @@ class VideoDetailView : ConstraintLayout {
|
|||||||
_liveTryJob = null;
|
_liveTryJob = null;
|
||||||
UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video), it, ::fetchVideo, null, fragment);
|
UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video), it, ::fetchVideo, null, fragment);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else TaskHandler(IPlatformVideoDetails::class.java, {fragment.lifecycleScope});
|
} else TaskHandler(IPlatformVideoDetails::class.java, {fragment.lifecycleScope});
|
||||||
|
|
||||||
private val _taskLoadRecommendations = TaskHandler<String?, IPager<IPlatformContent>?>(StateApp.instance.scopeGetter, {
|
private val _taskLoadRecommendations = TaskHandler<String?, IPager<IPlatformContent>?>(StateApp.instance.scopeGetter, {
|
||||||
|
@ -26,6 +26,7 @@ import com.futo.platformplayer.stores.FragmentedStorage
|
|||||||
import com.futo.platformplayer.stores.SearchHistoryStorage
|
import com.futo.platformplayer.stores.SearchHistoryStorage
|
||||||
|
|
||||||
class SearchTopBarFragment : TopFragment() {
|
class SearchTopBarFragment : TopFragment() {
|
||||||
|
@Suppress("PrivatePropertyName")
|
||||||
private val TAG = "SearchTopBarFragment"
|
private val TAG = "SearchTopBarFragment"
|
||||||
|
|
||||||
private var _editSearch: EditText? = null;
|
private var _editSearch: EditText? = null;
|
||||||
@ -191,29 +192,32 @@ class SearchTopBarFragment : TopFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onDone() {
|
private fun onDone() {
|
||||||
val editSearch = _editSearch;
|
val editSearch = _editSearch
|
||||||
if (editSearch != null) {
|
if (editSearch != null) {
|
||||||
val text = editSearch.text.toString();
|
val text = editSearch.text.toString()
|
||||||
if (text.length < 3) {
|
if (text.isEmpty()) {
|
||||||
UIDialogs.toast(getString(R.string.please_use_at_least_3_characters));
|
UIDialogs.toast(getString(R.string.please_use_at_least_1_character))
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
editSearch.clearFocus();
|
editSearch.clearFocus()
|
||||||
_inputMethodManager?.hideSoftInputFromWindow(editSearch.windowToken, 0);
|
_inputMethodManager?.hideSoftInputFromWindow(editSearch.windowToken, 0)
|
||||||
|
|
||||||
if (Settings.instance.search.searchHistory) {
|
if (Settings.instance.search.searchHistory) {
|
||||||
val storage = FragmentedStorage.get<SearchHistoryStorage>();
|
val storage = FragmentedStorage.get<SearchHistoryStorage>()
|
||||||
storage.add(text);
|
storage.add(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_searchType == SearchType.CREATOR) {
|
if (_searchType == SearchType.CREATOR) {
|
||||||
onSearch.emit(text);
|
onSearch.emit(text)
|
||||||
} else {
|
} else {
|
||||||
onSearch.emit(text);
|
onSearch.emit(text)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.w(TAG, "Unexpected condition happened where done is edit search is null but done is triggered.");
|
Logger.w(
|
||||||
|
TAG,
|
||||||
|
"Unexpected condition happened where done is edit search is null but done is triggered."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,13 +182,14 @@ class HLS {
|
|||||||
|
|
||||||
private fun parseAttributes(content: String): Map<String, String> {
|
private fun parseAttributes(content: String): Map<String, String> {
|
||||||
val attributes = mutableMapOf<String, String>()
|
val attributes = mutableMapOf<String, String>()
|
||||||
val attributePairs = content.substringAfter(":").splitToSequence(',')
|
val maybeAttributePairs = content.substringAfter(":").splitToSequence(',')
|
||||||
|
|
||||||
var currentPair = StringBuilder()
|
var currentPair = StringBuilder()
|
||||||
for (pair in attributePairs) {
|
for (pair in maybeAttributePairs) {
|
||||||
currentPair.append(pair)
|
currentPair.append(pair)
|
||||||
if (currentPair.count { it == '\"' } % 2 == 0) { // Check if the number of quotes is even
|
if (currentPair.count { it == '\"' } % 2 == 0) { // Check if the number of quotes is even
|
||||||
val (key, value) = currentPair.toString().split('=')
|
val key = currentPair.toString().substringBefore("=")
|
||||||
|
val value = currentPair.toString().substringAfter("=")
|
||||||
attributes[key.trim()] = value.trim().removeSurrounding("\"")
|
attributes[key.trim()] = value.trim().removeSurrounding("\"")
|
||||||
currentPair = StringBuilder() // Reset for the next attribute
|
currentPair = StringBuilder() // Reset for the next attribute
|
||||||
} else {
|
} else {
|
||||||
|
@ -89,7 +89,7 @@ class StateHistory {
|
|||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
||||||
Logger.i(TAG, "SyncHistory playback broadcasted (${liveObj.name}: ${position})");
|
Logger.i(TAG, "SyncHistory playback broadcasted (${liveObj.name}: ${position})");
|
||||||
StateSync.instance.broadcastJson(
|
StateSync.instance.broadcastJsonData(
|
||||||
GJSyncOpcodes.syncHistory,
|
GJSyncOpcodes.syncHistory,
|
||||||
listOf(historyVideo)
|
listOf(historyVideo)
|
||||||
);
|
);
|
||||||
|
@ -537,7 +537,7 @@ class StatePlatform {
|
|||||||
else getSortedEnabledClient().filter { if (it is JSClient) it.enableInSearch else true };
|
else getSortedEnabledClient().filter { if (it is JSClient) it.enableInSearch else true };
|
||||||
|
|
||||||
clients.parallelStream().forEach {
|
clients.parallelStream().forEach {
|
||||||
val searchCapabilities = it.getSearchCapabilities();
|
val searchCapabilities = it.getSearchChannelContentsCapabilities();
|
||||||
val mappedFilters = filters.map { pair -> Pair(pair.key, pair.value.map { v -> searchCapabilities.filters.first { g -> g.idOrName == pair.key }.filters.first { f -> f.idOrName == v }.value }) }.toMap();
|
val mappedFilters = filters.map { pair -> Pair(pair.key, pair.value.map { v -> searchCapabilities.filters.first { g -> g.idOrName == pair.key }.filters.first { f -> f.idOrName == v }.value }) }.toMap();
|
||||||
|
|
||||||
if (it.isChannelUrl(channelUrl)) {
|
if (it.isChannelUrl(channelUrl)) {
|
||||||
|
@ -198,7 +198,7 @@ class StatePlaylists {
|
|||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
||||||
Logger.i(StateSubscriptionGroups.TAG, "SyncPlaylist (${playlist.name})");
|
Logger.i(StateSubscriptionGroups.TAG, "SyncPlaylist (${playlist.name})");
|
||||||
StateSync.instance.broadcastJson(
|
StateSync.instance.broadcastJsonData(
|
||||||
GJSyncOpcodes.syncPlaylists,
|
GJSyncOpcodes.syncPlaylists,
|
||||||
SyncPlaylistsPackage(listOf(playlist), mapOf())
|
SyncPlaylistsPackage(listOf(playlist), mapOf())
|
||||||
);
|
);
|
||||||
@ -217,7 +217,7 @@ class StatePlaylists {
|
|||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
||||||
Logger.i(StateSubscriptionGroups.TAG, "SyncPlaylist (${playlist.name})");
|
Logger.i(StateSubscriptionGroups.TAG, "SyncPlaylist (${playlist.name})");
|
||||||
StateSync.instance.broadcastJson(
|
StateSync.instance.broadcastJsonData(
|
||||||
GJSyncOpcodes.syncPlaylists,
|
GJSyncOpcodes.syncPlaylists,
|
||||||
SyncPlaylistsPackage(listOf(), mapOf(Pair(playlist.id, OffsetDateTime.now().toEpochSecond())))
|
SyncPlaylistsPackage(listOf(), mapOf(Pair(playlist.id, OffsetDateTime.now().toEpochSecond())))
|
||||||
);
|
);
|
||||||
|
@ -81,7 +81,7 @@ class StateSubscriptionGroups {
|
|||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
||||||
Logger.i(TAG, "SyncSubscriptionGroup (${subGroup.name})");
|
Logger.i(TAG, "SyncSubscriptionGroup (${subGroup.name})");
|
||||||
StateSync.instance.broadcastJson(
|
StateSync.instance.broadcastJsonData(
|
||||||
GJSyncOpcodes.syncSubscriptionGroups,
|
GJSyncOpcodes.syncSubscriptionGroups,
|
||||||
SyncSubscriptionGroupsPackage(listOf(subGroup), mapOf())
|
SyncSubscriptionGroupsPackage(listOf(subGroup), mapOf())
|
||||||
);
|
);
|
||||||
@ -100,7 +100,7 @@ class StateSubscriptionGroups {
|
|||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
||||||
Logger.i(TAG, "SyncSubscriptionGroup delete (${group.name})");
|
Logger.i(TAG, "SyncSubscriptionGroup delete (${group.name})");
|
||||||
StateSync.instance.broadcastJson(
|
StateSync.instance.broadcastJsonData(
|
||||||
GJSyncOpcodes.syncSubscriptionGroups,
|
GJSyncOpcodes.syncSubscriptionGroups,
|
||||||
SyncSubscriptionGroupsPackage(listOf(), mapOf(Pair(id, OffsetDateTime.now().toEpochSecond())))
|
SyncSubscriptionGroupsPackage(listOf(), mapOf(Pair(id, OffsetDateTime.now().toEpochSecond())))
|
||||||
);
|
);
|
||||||
|
@ -250,7 +250,7 @@ class StateSubscriptions {
|
|||||||
|
|
||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
StateSync.instance.broadcast(
|
StateSync.instance.broadcastData(
|
||||||
GJSyncOpcodes.syncSubscriptions, Json.encodeToString(
|
GJSyncOpcodes.syncSubscriptions, Json.encodeToString(
|
||||||
SyncSubscriptionsPackage(
|
SyncSubscriptionsPackage(
|
||||||
listOf(subObj),
|
listOf(subObj),
|
||||||
@ -299,7 +299,7 @@ class StateSubscriptions {
|
|||||||
|
|
||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
StateSync.instance.broadcast(
|
StateSync.instance.broadcastData(
|
||||||
GJSyncOpcodes.syncSubscriptions, Json.encodeToString(
|
GJSyncOpcodes.syncSubscriptions, Json.encodeToString(
|
||||||
SyncSubscriptionsPackage(
|
SyncSubscriptionsPackage(
|
||||||
listOf(),
|
listOf(),
|
||||||
|
@ -370,26 +370,29 @@ class StateSync {
|
|||||||
Logger.i(TAG, "Connection authorized for ${remotePublicKey} because initiator")
|
Logger.i(TAG, "Connection authorized for ${remotePublicKey} because initiator")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onData = { s, opcode, data ->
|
onData = { s, opcode, subOpcode, data ->
|
||||||
session?.handlePacket(s, opcode, data)
|
session?.handlePacket(s, opcode, subOpcode, data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T> broadcastJson(opcode: UByte, data: T) {
|
inline fun <reified T> broadcastJsonData(subOpcode: UByte, data: T) {
|
||||||
broadcast(opcode, Json.encodeToString(data));
|
broadcast(SyncSocketSession.Opcode.DATA.value, subOpcode, Json.encodeToString(data));
|
||||||
}
|
}
|
||||||
fun broadcast(opcode: UByte, data: String) {
|
fun broadcastData(subOpcode: UByte, data: String) {
|
||||||
broadcast(opcode, data.toByteArray(Charsets.UTF_8));
|
broadcast(SyncSocketSession.Opcode.DATA.value, subOpcode, data.toByteArray(Charsets.UTF_8));
|
||||||
}
|
}
|
||||||
fun broadcast(opcode: UByte, data: ByteArray) {
|
fun broadcast(opcode: UByte, subOpcode: UByte, data: String) {
|
||||||
|
broadcast(opcode, subOpcode, data.toByteArray(Charsets.UTF_8));
|
||||||
|
}
|
||||||
|
fun broadcast(opcode: UByte, subOpcode: UByte, data: ByteArray) {
|
||||||
for(session in getSessions()) {
|
for(session in getSessions()) {
|
||||||
try {
|
try {
|
||||||
if (session.isAuthorized && session.connected) {
|
if (session.isAuthorized && session.connected) {
|
||||||
session.send(opcode, data);
|
session.send(opcode, subOpcode, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(ex: Exception) {
|
catch(ex: Exception) {
|
||||||
Logger.w(TAG, "Failed to broadcast ${opcode} to ${session.remotePublicKey}: ${ex.message}}", ex);
|
Logger.w(TAG, "Failed to broadcast (opcode = ${opcode}, subOpcode = ${subOpcode}) to ${session.remotePublicKey}: ${ex.message}}", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -398,7 +401,7 @@ class StateSync {
|
|||||||
val time = measureTimeMillis {
|
val time = measureTimeMillis {
|
||||||
//val export = StateBackup.export();
|
//val export = StateBackup.export();
|
||||||
//session.send(GJSyncOpcodes.syncExport, export.asZip());
|
//session.send(GJSyncOpcodes.syncExport, export.asZip());
|
||||||
session.send(GJSyncOpcodes.syncStateExchange, getSyncSessionDataString(session.remotePublicKey));
|
session.sendData(GJSyncOpcodes.syncStateExchange, getSyncSessionDataString(session.remotePublicKey));
|
||||||
}
|
}
|
||||||
Logger.i(TAG, "Generated and sent sync export in ${time}ms");
|
Logger.i(TAG, "Generated and sent sync export in ${time}ms");
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ class SyncSession : IAuthorizable {
|
|||||||
private val _onConnectedChanged: (session: SyncSession, connected: Boolean) -> Unit
|
private val _onConnectedChanged: (session: SyncSession, connected: Boolean) -> Unit
|
||||||
val remotePublicKey: String
|
val remotePublicKey: String
|
||||||
override val isAuthorized get() = _authorized && _remoteAuthorized
|
override val isAuthorized get() = _authorized && _remoteAuthorized
|
||||||
|
private var _wasAuthorized = false
|
||||||
|
|
||||||
var connected: Boolean = false
|
var connected: Boolean = false
|
||||||
private set(v) {
|
private set(v) {
|
||||||
@ -94,9 +95,11 @@ class SyncSession : IAuthorizable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkAuthorized() {
|
private fun checkAuthorized() {
|
||||||
if (isAuthorized)
|
if (!_wasAuthorized && isAuthorized) {
|
||||||
|
_wasAuthorized = true
|
||||||
_onAuthorized.invoke(this)
|
_onAuthorized.invoke(this)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun removeSocketSession(socketSession: SyncSocketSession) {
|
fun removeSocketSession(socketSession: SyncSocketSession) {
|
||||||
synchronized(_socketSessions) {
|
synchronized(_socketSessions) {
|
||||||
@ -117,8 +120,9 @@ class SyncSession : IAuthorizable {
|
|||||||
_onClose.invoke(this)
|
_onClose.invoke(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handlePacket(socketSession: SyncSocketSession, opcode: UByte, data: ByteBuffer) {
|
fun handlePacket(socketSession: SyncSocketSession, opcode: UByte, subOpcode: UByte, data: ByteBuffer) {
|
||||||
Logger.i(TAG, "Handle packet (opcode: ${opcode}, data.length: ${data.remaining()})")
|
try {
|
||||||
|
Logger.i(TAG, "Handle packet (opcode: ${opcode}, subOpcode: ${subOpcode}, data.length: ${data.remaining()})")
|
||||||
|
|
||||||
when (opcode) {
|
when (opcode) {
|
||||||
Opcode.NOTIFY_AUTHORIZED.value -> {
|
Opcode.NOTIFY_AUTHORIZED.value -> {
|
||||||
@ -136,10 +140,14 @@ class SyncSession : IAuthorizable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.i(TAG, "Received ${opcode} (${data.remaining()} bytes)")
|
if (opcode != Opcode.DATA.value) {
|
||||||
|
Logger.w(TAG, "Unknown opcode received: (opcode = ${opcode}, subOpcode = ${subOpcode})}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.i(TAG, "Received (opcode = ${opcode}, subOpcode = ${subOpcode}) (${data.remaining()} bytes)")
|
||||||
//TODO: Abstract this out
|
//TODO: Abstract this out
|
||||||
try {
|
when (subOpcode) {
|
||||||
when (opcode) {
|
|
||||||
GJSyncOpcodes.sendToDevices -> {
|
GJSyncOpcodes.sendToDevices -> {
|
||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
|
||||||
val context = StateApp.instance.contextOrNull;
|
val context = StateApp.instance.contextOrNull;
|
||||||
@ -164,13 +172,13 @@ class SyncSession : IAuthorizable {
|
|||||||
Logger.i(TAG, "Received SyncSessionData from " + remotePublicKey);
|
Logger.i(TAG, "Received SyncSessionData from " + remotePublicKey);
|
||||||
|
|
||||||
|
|
||||||
send(GJSyncOpcodes.syncSubscriptions, StateSubscriptions.instance.getSyncSubscriptionsPackageString());
|
sendData(GJSyncOpcodes.syncSubscriptions, StateSubscriptions.instance.getSyncSubscriptionsPackageString());
|
||||||
send(GJSyncOpcodes.syncSubscriptionGroups, StateSubscriptionGroups.instance.getSyncSubscriptionGroupsPackageString());
|
sendData(GJSyncOpcodes.syncSubscriptionGroups, StateSubscriptionGroups.instance.getSyncSubscriptionGroupsPackageString());
|
||||||
send(GJSyncOpcodes.syncPlaylists, StatePlaylists.instance.getSyncPlaylistsPackageString())
|
sendData(GJSyncOpcodes.syncPlaylists, StatePlaylists.instance.getSyncPlaylistsPackageString())
|
||||||
|
|
||||||
val recentHistory = StateHistory.instance.getRecentHistory(syncSessionData.lastHistory);
|
val recentHistory = StateHistory.instance.getRecentHistory(syncSessionData.lastHistory);
|
||||||
if(recentHistory.size > 0)
|
if(recentHistory.size > 0)
|
||||||
sendJson(GJSyncOpcodes.syncHistory, recentHistory);
|
sendJsonData(GJSyncOpcodes.syncHistory, recentHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
GJSyncOpcodes.syncExport -> {
|
GJSyncOpcodes.syncExport -> {
|
||||||
@ -338,16 +346,19 @@ class SyncSession : IAuthorizable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inline fun <reified T> sendJson(opcode: UByte, data: T) {
|
inline fun <reified T> sendJsonData(subOpcode: UByte, data: T) {
|
||||||
send(opcode, Json.encodeToString<T>(data));
|
send(Opcode.DATA.value, subOpcode, Json.encodeToString<T>(data));
|
||||||
}
|
}
|
||||||
fun send(opcode: UByte, data: String) {
|
fun sendData(subOpcode: UByte, data: String) {
|
||||||
send(opcode, data.toByteArray(Charsets.UTF_8));
|
send(Opcode.DATA.value, subOpcode, data.toByteArray(Charsets.UTF_8));
|
||||||
}
|
}
|
||||||
fun send(opcode: UByte, data: ByteArray) {
|
fun send(opcode: UByte, subOpcode: UByte, data: String) {
|
||||||
|
send(opcode, subOpcode, data.toByteArray(Charsets.UTF_8));
|
||||||
|
}
|
||||||
|
fun send(opcode: UByte, subOpcode: UByte, data: ByteArray) {
|
||||||
val sock = _socketSessions.firstOrNull();
|
val sock = _socketSessions.firstOrNull();
|
||||||
if(sock != null){
|
if(sock != null){
|
||||||
sock.send(opcode, ByteBuffer.wrap(data));
|
sock.send(opcode, subOpcode, ByteBuffer.wrap(data));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw IllegalStateException("Session has no active sockets");
|
throw IllegalStateException("Session has no active sockets");
|
||||||
|
@ -2,6 +2,7 @@ package com.futo.platformplayer.sync.internal
|
|||||||
|
|
||||||
import com.futo.platformplayer.LittleEndianDataInputStream
|
import com.futo.platformplayer.LittleEndianDataInputStream
|
||||||
import com.futo.platformplayer.LittleEndianDataOutputStream
|
import com.futo.platformplayer.LittleEndianDataOutputStream
|
||||||
|
import com.futo.platformplayer.ensureNotMainThread
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.noise.protocol.CipherStatePair
|
import com.futo.platformplayer.noise.protocol.CipherStatePair
|
||||||
import com.futo.platformplayer.noise.protocol.DHState
|
import com.futo.platformplayer.noise.protocol.DHState
|
||||||
@ -18,7 +19,8 @@ class SyncSocketSession {
|
|||||||
NOTIFY_UNAUTHORIZED(3u),
|
NOTIFY_UNAUTHORIZED(3u),
|
||||||
STREAM_START(4u),
|
STREAM_START(4u),
|
||||||
STREAM_DATA(5u),
|
STREAM_DATA(5u),
|
||||||
STREAM_END(6u)
|
STREAM_END(6u),
|
||||||
|
DATA(7u)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _inputStream: LittleEndianDataInputStream
|
private val _inputStream: LittleEndianDataInputStream
|
||||||
@ -41,12 +43,12 @@ class SyncSocketSession {
|
|||||||
private val _localKeyPair: DHState
|
private val _localKeyPair: DHState
|
||||||
private var _localPublicKey: String
|
private var _localPublicKey: String
|
||||||
val localPublicKey: String get() = _localPublicKey
|
val localPublicKey: String get() = _localPublicKey
|
||||||
private val _onData: (session: SyncSocketSession, opcode: UByte, data: ByteBuffer) -> Unit
|
private val _onData: (session: SyncSocketSession, opcode: UByte, subOpcode: UByte, data: ByteBuffer) -> Unit
|
||||||
var authorizable: IAuthorizable? = null
|
var authorizable: IAuthorizable? = null
|
||||||
|
|
||||||
val remoteAddress: String
|
val remoteAddress: String
|
||||||
|
|
||||||
constructor(remoteAddress: String, localKeyPair: DHState, inputStream: LittleEndianDataInputStream, outputStream: LittleEndianDataOutputStream, onClose: (session: SyncSocketSession) -> Unit, onHandshakeComplete: (session: SyncSocketSession) -> Unit, onData: (session: SyncSocketSession, opcode: UByte, data: ByteBuffer) -> Unit) {
|
constructor(remoteAddress: String, localKeyPair: DHState, inputStream: LittleEndianDataInputStream, outputStream: LittleEndianDataOutputStream, onClose: (session: SyncSocketSession) -> Unit, onHandshakeComplete: (session: SyncSocketSession) -> Unit, onData: (session: SyncSocketSession, opcode: UByte, subOpcode: UByte, data: ByteBuffer) -> Unit) {
|
||||||
_inputStream = inputStream
|
_inputStream = inputStream
|
||||||
_outputStream = outputStream
|
_outputStream = outputStream
|
||||||
_onClose = onClose
|
_onClose = onClose
|
||||||
@ -159,10 +161,11 @@ class SyncSocketSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun performVersionCheck() {
|
private fun performVersionCheck() {
|
||||||
_outputStream.writeInt(1)
|
val CURRENT_VERSION = 2
|
||||||
|
_outputStream.writeInt(CURRENT_VERSION)
|
||||||
val version = _inputStream.readInt()
|
val version = _inputStream.readInt()
|
||||||
Logger.i(TAG, "performVersionCheck (version = $version)")
|
Logger.i(TAG, "performVersionCheck (version = $version)")
|
||||||
if (version != 1)
|
if (version != CURRENT_VERSION)
|
||||||
throw Exception("Invalid version")
|
throw Exception("Invalid version")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,8 +208,9 @@ class SyncSocketSession {
|
|||||||
throw Exception("Handshake finished without completing")
|
throw Exception("Handshake finished without completing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun send(opcode: UByte, subOpcode: UByte, data: ByteBuffer) {
|
||||||
|
ensureNotMainThread()
|
||||||
|
|
||||||
fun send(opcode: UByte, data: ByteBuffer) {
|
|
||||||
if (data.remaining() + HEADER_SIZE > MAXIMUM_PACKET_SIZE) {
|
if (data.remaining() + HEADER_SIZE > MAXIMUM_PACKET_SIZE) {
|
||||||
val segmentSize = MAXIMUM_PACKET_SIZE - HEADER_SIZE
|
val segmentSize = MAXIMUM_PACKET_SIZE - HEADER_SIZE
|
||||||
val segmentData = ByteArray(segmentSize)
|
val segmentData = ByteArray(segmentSize)
|
||||||
@ -223,8 +227,8 @@ class SyncSocketSession {
|
|||||||
|
|
||||||
if (sendOffset == 0) {
|
if (sendOffset == 0) {
|
||||||
segmentOpcode = Opcode.STREAM_START.value
|
segmentOpcode = Opcode.STREAM_START.value
|
||||||
bytesToSend = segmentSize - 4 - 4 - 1
|
bytesToSend = segmentSize - 4 - 4 - 1 - 1
|
||||||
segmentPacketSize = bytesToSend + 4 + 4 + 1
|
segmentPacketSize = bytesToSend + 4 + 4 + 1 + 1
|
||||||
} else {
|
} else {
|
||||||
bytesToSend = minOf(segmentSize - 4 - 4, bytesRemaining)
|
bytesToSend = minOf(segmentSize - 4 - 4, bytesRemaining)
|
||||||
segmentOpcode = if (bytesToSend >= bytesRemaining) Opcode.STREAM_END.value else Opcode.STREAM_DATA.value
|
segmentOpcode = if (bytesToSend >= bytesRemaining) Opcode.STREAM_END.value else Opcode.STREAM_DATA.value
|
||||||
@ -236,18 +240,20 @@ class SyncSocketSession {
|
|||||||
putInt(if (segmentOpcode == Opcode.STREAM_START.value) data.remaining() else sendOffset)
|
putInt(if (segmentOpcode == Opcode.STREAM_START.value) data.remaining() else sendOffset)
|
||||||
if (segmentOpcode == Opcode.STREAM_START.value) {
|
if (segmentOpcode == Opcode.STREAM_START.value) {
|
||||||
put(opcode.toByte())
|
put(opcode.toByte())
|
||||||
|
put(subOpcode.toByte())
|
||||||
}
|
}
|
||||||
put(data.array(), data.position() + sendOffset, bytesToSend)
|
put(data.array(), data.position() + sendOffset, bytesToSend)
|
||||||
}
|
}
|
||||||
|
|
||||||
send(segmentOpcode, ByteBuffer.wrap(segmentData, 0, segmentPacketSize))
|
send(segmentOpcode, 0u, ByteBuffer.wrap(segmentData, 0, segmentPacketSize))
|
||||||
sendOffset += bytesToSend
|
sendOffset += bytesToSend
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
synchronized(_sendLockObject) {
|
synchronized(_sendLockObject) {
|
||||||
ByteBuffer.wrap(_sendBuffer).order(ByteOrder.LITTLE_ENDIAN).apply {
|
ByteBuffer.wrap(_sendBuffer).order(ByteOrder.LITTLE_ENDIAN).apply {
|
||||||
putInt(data.remaining() + 1)
|
putInt(data.remaining() + 2)
|
||||||
put(opcode.toByte())
|
put(opcode.toByte())
|
||||||
|
put(subOpcode.toByte())
|
||||||
put(data.array(), data.position(), data.remaining())
|
put(data.array(), data.position(), data.remaining())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,12 +266,15 @@ class SyncSocketSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun send(opcode: UByte) {
|
fun send(opcode: UByte, subOpcode: UByte = 0u) {
|
||||||
synchronized(_sendLockObject) {
|
ensureNotMainThread()
|
||||||
ByteBuffer.wrap(_sendBuffer, 0, 4).order(ByteOrder.LITTLE_ENDIAN).putInt(1)
|
|
||||||
_sendBuffer.asUByteArray()[4] = opcode
|
|
||||||
|
|
||||||
//Logger.i(TAG, "Encrypting message (size = ${HEADER_SIZE})")
|
synchronized(_sendLockObject) {
|
||||||
|
ByteBuffer.wrap(_sendBuffer, 0, 4).order(ByteOrder.LITTLE_ENDIAN).putInt(2)
|
||||||
|
_sendBuffer.asUByteArray()[4] = opcode
|
||||||
|
_sendBuffer.asUByteArray()[5] = subOpcode
|
||||||
|
|
||||||
|
//Logger.i(TAG, "Encrypting message (opcode = ${opcode}, subOpcode = ${subOpcode}, size = ${HEADER_SIZE})")
|
||||||
|
|
||||||
val len = _cipherStatePair!!.sender.encryptWithAd(null, _sendBuffer, 0, _sendBufferEncrypted, 0, HEADER_SIZE)
|
val len = _cipherStatePair!!.sender.encryptWithAd(null, _sendBuffer, 0, _sendBufferEncrypted, 0, HEADER_SIZE)
|
||||||
//Logger.i(TAG, "Sending encrypted message (size = ${len})")
|
//Logger.i(TAG, "Sending encrypted message (size = ${len})")
|
||||||
@ -277,19 +286,19 @@ class SyncSocketSession {
|
|||||||
|
|
||||||
private fun handleData(data: ByteArray, length: Int) {
|
private fun handleData(data: ByteArray, length: Int) {
|
||||||
if (length < HEADER_SIZE)
|
if (length < HEADER_SIZE)
|
||||||
throw Exception("Packet must be at least 5 bytes (header size)")
|
throw Exception("Packet must be at least 6 bytes (header size)")
|
||||||
|
|
||||||
val size = ByteBuffer.wrap(data, 0, 4).order(ByteOrder.LITTLE_ENDIAN).int
|
val size = ByteBuffer.wrap(data, 0, 4).order(ByteOrder.LITTLE_ENDIAN).int
|
||||||
if (size != length - 4)
|
if (size != length - 4)
|
||||||
throw Exception("Incomplete packet received")
|
throw Exception("Incomplete packet received")
|
||||||
|
|
||||||
val opcode = data.asUByteArray()[4]
|
val opcode = data.asUByteArray()[4]
|
||||||
val packetData = ByteBuffer.wrap(data, HEADER_SIZE, size - 1)
|
val subOpcode = data.asUByteArray()[5]
|
||||||
|
val packetData = ByteBuffer.wrap(data, HEADER_SIZE, size - 2)
|
||||||
handlePacket(opcode, packetData.order(ByteOrder.LITTLE_ENDIAN))
|
handlePacket(opcode, subOpcode, packetData.order(ByteOrder.LITTLE_ENDIAN))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePacket(opcode: UByte, data: ByteBuffer) {
|
private fun handlePacket(opcode: UByte, subOpcode: UByte, data: ByteBuffer) {
|
||||||
when (opcode) {
|
when (opcode) {
|
||||||
Opcode.PING.value -> {
|
Opcode.PING.value -> {
|
||||||
send(Opcode.PONG.value)
|
send(Opcode.PONG.value)
|
||||||
@ -302,7 +311,7 @@ class SyncSocketSession {
|
|||||||
}
|
}
|
||||||
Opcode.NOTIFY_AUTHORIZED.value,
|
Opcode.NOTIFY_AUTHORIZED.value,
|
||||||
Opcode.NOTIFY_UNAUTHORIZED.value -> {
|
Opcode.NOTIFY_UNAUTHORIZED.value -> {
|
||||||
_onData.invoke(this, opcode, data)
|
_onData.invoke(this, opcode, subOpcode, data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -316,8 +325,9 @@ class SyncSocketSession {
|
|||||||
val id = data.int
|
val id = data.int
|
||||||
val expectedSize = data.int
|
val expectedSize = data.int
|
||||||
val op = data.get().toUByte()
|
val op = data.get().toUByte()
|
||||||
|
val subOp = data.get().toUByte()
|
||||||
|
|
||||||
val syncStream = SyncStream(expectedSize, op)
|
val syncStream = SyncStream(expectedSize, op, subOp)
|
||||||
if (data.remaining() > 0) {
|
if (data.remaining() > 0) {
|
||||||
syncStream.add(data.array(), data.position(), data.remaining())
|
syncStream.add(data.array(), data.position(), data.remaining())
|
||||||
}
|
}
|
||||||
@ -362,10 +372,13 @@ class SyncSocketSession {
|
|||||||
throw Exception("After sync stream end, the stream must be complete")
|
throw Exception("After sync stream end, the stream must be complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePacket(syncStream.opcode, syncStream.getBytes().let { ByteBuffer.wrap(it).order(ByteOrder.LITTLE_ENDIAN) })
|
handlePacket(syncStream.opcode, syncStream.subOpcode, syncStream.getBytes().let { ByteBuffer.wrap(it).order(ByteOrder.LITTLE_ENDIAN) })
|
||||||
|
}
|
||||||
|
Opcode.DATA.value -> {
|
||||||
|
_onData.invoke(this, opcode, subOpcode, data)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
_onData.invoke(this, opcode, data)
|
Logger.w(TAG, "Unknown opcode received (opcode = ${opcode}, subOpcode = ${subOpcode})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -374,6 +387,6 @@ class SyncSocketSession {
|
|||||||
private const val TAG = "SyncSocketSession"
|
private const val TAG = "SyncSocketSession"
|
||||||
const val MAXIMUM_PACKET_SIZE = 65535 - 16
|
const val MAXIMUM_PACKET_SIZE = 65535 - 16
|
||||||
const val MAXIMUM_PACKET_SIZE_ENCRYPTED = MAXIMUM_PACKET_SIZE + 16
|
const val MAXIMUM_PACKET_SIZE_ENCRYPTED = MAXIMUM_PACKET_SIZE + 16
|
||||||
const val HEADER_SIZE = 5
|
const val HEADER_SIZE = 6
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package com.futo.platformplayer.sync.internal
|
package com.futo.platformplayer.sync.internal
|
||||||
|
|
||||||
class SyncStream(expectedSize: Int, val opcode: UByte) {
|
class SyncStream(expectedSize: Int, val opcode: UByte, val subOpcode: UByte) {
|
||||||
companion object {
|
companion object {
|
||||||
const val MAXIMUM_SIZE = 10_000_000
|
const val MAXIMUM_SIZE = 10_000_000
|
||||||
}
|
}
|
||||||
|
@ -327,8 +327,8 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
|||||||
return _chapters?.let { chaps -> chaps.find { pos.toDouble() / 1000 > it.timeStart && pos.toDouble() / 1000 < it.timeEnd && (toIgnore.isEmpty() || !toIgnore.contains(it)) } };
|
return _chapters?.let { chaps -> chaps.find { pos.toDouble() / 1000 > it.timeStart && pos.toDouble() / 1000 < it.timeEnd && (toIgnore.isEmpty() || !toIgnore.contains(it)) } };
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSource(videoSource: IVideoSource?, audioSource: IAudioSource? = null, play: Boolean = false, keepSubtitles: Boolean = false) {
|
fun setSource(videoSource: IVideoSource?, audioSource: IAudioSource? = null, play: Boolean = false, keepSubtitles: Boolean = false, resume: Boolean = false) {
|
||||||
swapSources(videoSource, audioSource,false, play, keepSubtitles);
|
swapSources(videoSource, audioSource,resume, play, keepSubtitles);
|
||||||
}
|
}
|
||||||
fun swapSources(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true, keepSubtitles: Boolean = false): Boolean {
|
fun swapSources(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true, keepSubtitles: Boolean = false): Boolean {
|
||||||
var videoSourceUsed = videoSource;
|
var videoSourceUsed = videoSource;
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:paddingRight="20dp"
|
android:paddingRight="20dp"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp" />
|
app:srcCompat="@drawable/ic_back_thin_white_16dp" />
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:paddingRight="20dp"
|
android:paddingRight="20dp"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp" />
|
app:srcCompat="@drawable/ic_back_thin_white_16dp" />
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:paddingRight="20dp"
|
android:paddingRight="20dp"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp" />
|
app:srcCompat="@drawable/ic_back_thin_white_16dp" />
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
android:id="@+id/button_close"
|
android:id="@+id/button_close"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_close"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
@ -75,6 +75,7 @@
|
|||||||
android:id="@+id/incognito_button"
|
android:id="@+id/incognito_button"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_incognito_button"
|
||||||
android:src="@drawable/ic_disabled_visible_purple"
|
android:src="@drawable/ic_disabled_visible_purple"
|
||||||
android:background="@drawable/background_button_round_black"
|
android:background="@drawable/background_button_round_black"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:paddingRight="20dp"
|
android:paddingRight="20dp"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp" />
|
app:srcCompat="@drawable/ic_back_thin_white_16dp" />
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
||||||
@ -19,6 +20,7 @@
|
|||||||
android:id="@+id/button_help"
|
android:id="@+id/button_help"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/cd_button_help"
|
||||||
app:srcCompat="@drawable/ic_help"
|
app:srcCompat="@drawable/ic_help"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
||||||
@ -19,6 +20,7 @@
|
|||||||
android:id="@+id/button_help"
|
android:id="@+id/button_help"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/cd_button_help"
|
||||||
app:srcCompat="@drawable/ic_help"
|
app:srcCompat="@drawable/ic_help"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
||||||
@ -19,6 +20,7 @@
|
|||||||
android:id="@+id/button_help"
|
android:id="@+id/button_help"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_help"
|
||||||
app:srcCompat="@drawable/ic_help"
|
app:srcCompat="@drawable/ic_help"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
||||||
@ -20,6 +21,7 @@
|
|||||||
android:id="@+id/button_help"
|
android:id="@+id/button_help"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/cd_button_help"
|
||||||
app:srcCompat="@drawable/ic_help"
|
app:srcCompat="@drawable/ic_help"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
@ -28,6 +30,7 @@
|
|||||||
android:id="@+id/image_polycentric"
|
android:id="@+id/image_polycentric"
|
||||||
android:layout_height="80dp"
|
android:layout_height="80dp"
|
||||||
android:layout_width="80dp"
|
android:layout_width="80dp"
|
||||||
|
android:contentDescription="@string/cd_image_polycentric"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
app:shapeAppearanceOverlay="@style/roundedCorners_40dp"
|
app:shapeAppearanceOverlay="@style/roundedCorners_40dp"
|
||||||
app:srcCompat="@drawable/placeholder_profile"
|
app:srcCompat="@drawable/placeholder_profile"
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:paddingRight="20dp"
|
android:paddingRight="20dp"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp" />
|
app:srcCompat="@drawable/ic_back_thin_white_16dp" />
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
android:id="@+id/button_cancel"
|
android:id="@+id/button_cancel"
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
android:layout_height="20dp"
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@string/cd_button_close"
|
||||||
app:srcCompat="@drawable/ic_close_thin"
|
app:srcCompat="@drawable/ic_close_thin"
|
||||||
app:tint="#888888"
|
app:tint="#888888"
|
||||||
android:layout_marginEnd="30dp" />
|
android:layout_marginEnd="30dp" />
|
||||||
|
@ -97,6 +97,7 @@
|
|||||||
android:id="@+id/button_scan_qr"
|
android:id="@+id/button_scan_qr"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_scan_qr"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
app:srcCompat="@drawable/ic_qr"
|
app:srcCompat="@drawable/ic_qr"
|
||||||
app:tint="@color/primary" />
|
app:tint="@color/primary" />
|
||||||
@ -109,6 +110,7 @@
|
|||||||
android:id="@+id/button_add"
|
android:id="@+id/button_add"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_add"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
app:srcCompat="@drawable/ic_add"
|
app:srcCompat="@drawable/ic_add"
|
||||||
app:tint="@color/primary"
|
app:tint="@color/primary"
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
android:id="@+id/image_device"
|
android:id="@+id/image_device"
|
||||||
android:layout_width="25dp"
|
android:layout_width="25dp"
|
||||||
android:layout_height="25dp"
|
android:layout_height="25dp"
|
||||||
|
android:contentDescription="@string/cd_image_device"
|
||||||
app:srcCompat="@drawable/ic_chromecast"
|
app:srcCompat="@drawable/ic_chromecast"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
@ -197,6 +198,7 @@
|
|||||||
android:id="@id/button_previous"
|
android:id="@id/button_previous"
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
|
android:contentDescription="@string/cd_button_previous"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
@ -206,6 +208,7 @@
|
|||||||
android:id="@+id/button_play"
|
android:id="@+id/button_play"
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
|
android:contentDescription="@string/cd_button_play"
|
||||||
android:padding="20dp"
|
android:padding="20dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
@ -215,6 +218,7 @@
|
|||||||
android:id="@+id/button_pause"
|
android:id="@+id/button_pause"
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
|
android:contentDescription="@string/cd_button_pause"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
@ -224,6 +228,7 @@
|
|||||||
android:id="@+id/button_stop"
|
android:id="@+id/button_stop"
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
|
android:contentDescription="@string/cd_button_stop"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:padding="5dp"
|
android:padding="5dp"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
@ -233,6 +238,7 @@
|
|||||||
android:id="@id/button_next"
|
android:id="@id/button_next"
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
|
android:contentDescription="@string/cd_button_next"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
android:id="@+id/update_spinner"
|
android:id="@+id/update_spinner"
|
||||||
android:layout_width="100dp"
|
android:layout_width="100dp"
|
||||||
android:layout_height="100dp"
|
android:layout_height="100dp"
|
||||||
|
android:contentDescription="@string/cd_update_spinner"
|
||||||
app:srcCompat="@drawable/ic_update_animated" />
|
app:srcCompat="@drawable/ic_update_animated" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
android:id="@+id/app_icon"
|
android:id="@+id/app_icon"
|
||||||
android:layout_width="35dp"
|
android:layout_width="35dp"
|
||||||
android:layout_height="35dp"
|
android:layout_height="35dp"
|
||||||
|
android:contentDescription="@string/cd_app_icon"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
@ -72,6 +73,7 @@
|
|||||||
android:id="@+id/button_cast"
|
android:id="@+id/button_cast"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_cast_button"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingEnd="12dp"
|
android:paddingEnd="12dp"
|
||||||
android:paddingTop="12dp"
|
android:paddingTop="12dp"
|
||||||
@ -84,6 +86,7 @@
|
|||||||
android:id="@+id/button_add"
|
android:id="@+id/button_add"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_add"
|
||||||
android:paddingStart="5dp"
|
android:paddingStart="5dp"
|
||||||
android:paddingEnd="12dp"
|
android:paddingEnd="12dp"
|
||||||
android:paddingTop="7dp"
|
android:paddingTop="7dp"
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
android:background="@drawable/rounded_outline"
|
android:background="@drawable/rounded_outline"
|
||||||
android:layout_width="35dp"
|
android:layout_width="35dp"
|
||||||
android:layout_height="35dp"
|
android:layout_height="35dp"
|
||||||
|
android:contentDescription="@string/cd_creator_thumbnail"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
@ -103,6 +104,7 @@
|
|||||||
android:id="@+id/button_sub_settings"
|
android:id="@+id/button_sub_settings"
|
||||||
android:layout_width="30dp"
|
android:layout_width="30dp"
|
||||||
android:layout_height="30dp"
|
android:layout_height="30dp"
|
||||||
|
android:contentDescription="@string/cd_button_settings"
|
||||||
android:layout_marginTop="3dp"
|
android:layout_marginTop="3dp"
|
||||||
android:layout_marginRight="10dp"
|
android:layout_marginRight="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
@ -114,6 +116,7 @@
|
|||||||
android:id="@+id/button_subscribe"
|
android:id="@+id/button_subscribe"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/cd_button_subscribe"
|
||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
android:id="@+id/image_channel_thumbnail"
|
android:id="@+id/image_channel_thumbnail"
|
||||||
android:layout_width="80dp"
|
android:layout_width="80dp"
|
||||||
android:layout_height="80dp"
|
android:layout_height="80dp"
|
||||||
|
android:contentDescription="@string/cd_creator_thumbnail"
|
||||||
app:srcCompat="@drawable/ic_peertube"
|
app:srcCompat="@drawable/ic_peertube"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
android:id="@+id/button_clear_search"
|
android:id="@+id/button_clear_search"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_clear_search"
|
||||||
android:paddingStart="18dp"
|
android:paddingStart="18dp"
|
||||||
android:paddingEnd="18dp"
|
android:paddingEnd="18dp"
|
||||||
android:layout_gravity="right|center_vertical"
|
android:layout_gravity="right|center_vertical"
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="26dp"
|
android:layout_width="26dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_search_icon"
|
||||||
app:srcCompat="@drawable/ic_search_thin"
|
app:srcCompat="@drawable/ic_search_thin"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
@ -65,6 +66,7 @@
|
|||||||
android:id="@+id/button_clear_search"
|
android:id="@+id/button_clear_search"
|
||||||
android:layout_width="46dp"
|
android:layout_width="46dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_clear_search"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:srcCompat="@drawable/ic_close_thin"
|
app:srcCompat="@drawable/ic_close_thin"
|
||||||
app:tint="@color/gray_ac"
|
app:tint="@color/gray_ac"
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:paddingLeft="16dp"
|
android:paddingLeft="16dp"
|
||||||
android:paddingRight="8dp"
|
android:paddingRight="8dp"
|
||||||
app:srcCompat="@drawable/ic_back_nav" />
|
app:srcCompat="@drawable/ic_back_nav" />
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:paddingLeft="16dp"
|
android:paddingLeft="16dp"
|
||||||
android:paddingRight="8dp"
|
android:paddingRight="8dp"
|
||||||
app:srcCompat="@drawable/ic_back_nav" />
|
app:srcCompat="@drawable/ic_back_nav" />
|
||||||
@ -34,6 +35,7 @@
|
|||||||
android:id="@+id/button_cast"
|
android:id="@+id/button_cast"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_cast_button"
|
||||||
android:paddingStart="4dp"
|
android:paddingStart="4dp"
|
||||||
android:paddingEnd="4dp"
|
android:paddingEnd="4dp"
|
||||||
android:paddingTop="9dp"
|
android:paddingTop="9dp"
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
android:id="@+id/app_icon"
|
android:id="@+id/app_icon"
|
||||||
android:layout_width="35dp"
|
android:layout_width="35dp"
|
||||||
android:layout_height="35dp"
|
android:layout_height="35dp"
|
||||||
|
android:contentDescription="@string/cd_app_icon"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
@ -37,6 +38,7 @@
|
|||||||
android:id="@+id/button_cast"
|
android:id="@+id/button_cast"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_cast_button"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingEnd="12dp"
|
android:paddingEnd="12dp"
|
||||||
android:paddingTop="12dp"
|
android:paddingTop="12dp"
|
||||||
@ -49,6 +51,7 @@
|
|||||||
android:id="@+id/button_search"
|
android:id="@+id/button_search"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_search"
|
||||||
android:paddingStart="5dp"
|
android:paddingStart="5dp"
|
||||||
android:paddingEnd="12dp"
|
android:paddingEnd="12dp"
|
||||||
android:paddingTop="11dp"
|
android:paddingTop="11dp"
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
android:id="@+id/image_history"
|
android:id="@+id/image_history"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/cd_icon_history"
|
||||||
app:srcCompat="@drawable/ic_clock_white"
|
app:srcCompat="@drawable/ic_clock_white"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
@ -119,6 +120,7 @@
|
|||||||
android:id="@+id/button_create_playlist"
|
android:id="@+id/button_create_playlist"
|
||||||
android:layout_width="35dp"
|
android:layout_width="35dp"
|
||||||
android:layout_height="20dp"
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@string/cd_button_create_playlist"
|
||||||
app:srcCompat="@drawable/ic_add_white_16dp"
|
app:srcCompat="@drawable/ic_add_white_16dp"
|
||||||
android:paddingEnd="15dp"
|
android:paddingEnd="15dp"
|
||||||
android:paddingStart="15dp"
|
android:paddingStart="15dp"
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
android:id="@+id/button_share"
|
android:id="@+id/button_share"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_share"
|
||||||
android:background="@drawable/background_button_round"
|
android:background="@drawable/background_button_round"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="5dp"
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
android:id="@+id/button_back"
|
android:id="@+id/button_back"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_back"
|
||||||
android:paddingLeft="16dp"
|
android:paddingLeft="16dp"
|
||||||
android:paddingRight="16dp"
|
android:paddingRight="16dp"
|
||||||
app:srcCompat="@drawable/ic_back_white_24dp" />
|
app:srcCompat="@drawable/ic_back_white_24dp" />
|
||||||
@ -27,6 +28,7 @@
|
|||||||
android:id="@+id/edit_search"
|
android:id="@+id/edit_search"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Search"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone"
|
||||||
@ -37,6 +39,7 @@
|
|||||||
android:id="@+id/button_clear_search"
|
android:id="@+id/button_clear_search"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_clear_search"
|
||||||
android:paddingStart="18dp"
|
android:paddingStart="18dp"
|
||||||
android:paddingEnd="18dp"
|
android:paddingEnd="18dp"
|
||||||
android:layout_gravity="right|center_vertical"
|
android:layout_gravity="right|center_vertical"
|
||||||
@ -48,6 +51,7 @@
|
|||||||
android:id="@+id/button_filter"
|
android:id="@+id/button_filter"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_filter"
|
||||||
android:paddingStart="8dp"
|
android:paddingStart="8dp"
|
||||||
android:paddingEnd="8dp"
|
android:paddingEnd="8dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
android:id="@+id/button_delete"
|
android:id="@+id/button_delete"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_delete"
|
||||||
android:layout_marginLeft="5dp"
|
android:layout_marginLeft="5dp"
|
||||||
android:layout_marginRight="0dp"
|
android:layout_marginRight="0dp"
|
||||||
android:src="@drawable/ic_trash"
|
android:src="@drawable/ic_trash"
|
||||||
@ -56,6 +57,7 @@
|
|||||||
android:id="@+id/button_settings"
|
android:id="@+id/button_settings"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_settings"
|
||||||
android:layout_marginLeft="5dp"
|
android:layout_marginLeft="5dp"
|
||||||
android:layout_marginRight="5dp"
|
android:layout_marginRight="5dp"
|
||||||
android:src="@drawable/ic_settings"
|
android:src="@drawable/ic_settings"
|
||||||
@ -69,6 +71,7 @@
|
|||||||
android:id="@+id/image_group"
|
android:id="@+id/image_group"
|
||||||
android:layout_width="110dp"
|
android:layout_width="110dp"
|
||||||
android:layout_height="70dp"
|
android:layout_height="70dp"
|
||||||
|
android:contentDescription="@string/cd_image_group"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
app:circularflow_defaultRadius="10dp"
|
app:circularflow_defaultRadius="10dp"
|
||||||
android:layout_marginLeft="30dp"
|
android:layout_marginLeft="30dp"
|
||||||
@ -90,6 +93,7 @@
|
|||||||
<ImageButton
|
<ImageButton
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_edit_image"
|
||||||
android:padding="5dp"
|
android:padding="5dp"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
@ -121,6 +125,7 @@
|
|||||||
<ImageButton
|
<ImageButton
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
android:layout_height="20dp"
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@string/cd_button_edit"
|
||||||
android:padding="2dp"
|
android:padding="2dp"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="5dp"
|
||||||
android:layout_marginBottom="-5dp"
|
android:layout_marginBottom="-5dp"
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
android:id="@+id/button_share"
|
android:id="@+id/button_share"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_share"
|
||||||
android:background="@drawable/background_button_round"
|
android:background="@drawable/background_button_round"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="5dp"
|
||||||
@ -162,6 +163,7 @@
|
|||||||
android:id="@+id/button_edit"
|
android:id="@+id/button_edit"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_edit"
|
||||||
android:background="@drawable/background_button_round"
|
android:background="@drawable/background_button_round"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="5dp"
|
||||||
@ -177,6 +179,7 @@
|
|||||||
android:id="@+id/button_download"
|
android:id="@+id/button_download"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_download"
|
||||||
android:background="@drawable/background_button_round"
|
android:background="@drawable/background_button_round"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
|
@ -36,7 +36,8 @@
|
|||||||
<com.futo.platformplayer.views.others.CreatorThumbnail
|
<com.futo.platformplayer.views.others.CreatorThumbnail
|
||||||
android:id="@+id/creator_thumbnail"
|
android:id="@+id/creator_thumbnail"
|
||||||
android:layout_width="27dp"
|
android:layout_width="27dp"
|
||||||
android:layout_height="27dp" />
|
android:layout_height="27dp"
|
||||||
|
android:contentDescription="@string/cd_creator_thumbnail" />
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -128,6 +129,7 @@
|
|||||||
android:id="@+id/image_like_icon"
|
android:id="@+id/image_like_icon"
|
||||||
android:layout_width="18dp"
|
android:layout_width="18dp"
|
||||||
android:layout_height="18dp"
|
android:layout_height="18dp"
|
||||||
|
android:contentDescription="@string/cd_image_like_icon"
|
||||||
app:srcCompat="@drawable/ic_thumb_up" />
|
app:srcCompat="@drawable/ic_thumb_up" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -144,6 +146,7 @@
|
|||||||
android:id="@+id/image_dislike_icon"
|
android:id="@+id/image_dislike_icon"
|
||||||
android:layout_width="18dp"
|
android:layout_width="18dp"
|
||||||
android:layout_height="18dp"
|
android:layout_height="18dp"
|
||||||
|
android:contentDescription="@string/cd_image_dislike_icon"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginTop="2dp"
|
android:layout_marginTop="2dp"
|
||||||
app:srcCompat="@drawable/ic_thumb_down" />
|
app:srcCompat="@drawable/ic_thumb_down" />
|
||||||
@ -285,6 +288,7 @@
|
|||||||
android:id="@+id/button_share"
|
android:id="@+id/button_share"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="32dp"
|
android:layout_height="32dp"
|
||||||
|
android:contentDescription="@string/cd_button_share"
|
||||||
android:background="@drawable/background_button_round"
|
android:background="@drawable/background_button_round"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="5dp"
|
||||||
|
@ -103,6 +103,7 @@
|
|||||||
android:id="@+id/minimize_play"
|
android:id="@+id/minimize_play"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_minimize_play"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
@ -111,6 +112,7 @@
|
|||||||
android:id="@+id/minimize_pause"
|
android:id="@+id/minimize_pause"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_minimize_pause"
|
||||||
android:padding="5dp"
|
android:padding="5dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
@ -119,6 +121,7 @@
|
|||||||
android:id="@+id/minimize_close"
|
android:id="@+id/minimize_close"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_minimize_close"
|
||||||
android:padding="5dp"
|
android:padding="5dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:layout_marginStart="2dp"
|
android:layout_marginStart="2dp"
|
||||||
@ -337,7 +340,8 @@
|
|||||||
<com.futo.platformplayer.views.others.CreatorThumbnail
|
<com.futo.platformplayer.views.others.CreatorThumbnail
|
||||||
android:id="@+id/creator_thumbnail"
|
android:id="@+id/creator_thumbnail"
|
||||||
android:layout_width="35dp"
|
android:layout_width="35dp"
|
||||||
android:layout_height="35dp" />
|
android:layout_height="35dp"
|
||||||
|
android:contentDescription="@string/cd_creator_thumbnail" />
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
android:id="@+id/donation_amount"
|
android:id="@+id/donation_amount"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/cd_donation_amount"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
tools:text="$100" />
|
tools:text="$100" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -100,6 +100,7 @@
|
|||||||
android:id="@+id/image_like_icon"
|
android:id="@+id/image_like_icon"
|
||||||
android:layout_width="18dp"
|
android:layout_width="18dp"
|
||||||
android:layout_height="18dp"
|
android:layout_height="18dp"
|
||||||
|
android:contentDescription="@string/cd_image_like_icon"
|
||||||
app:srcCompat="@drawable/ic_thumb_up" />
|
app:srcCompat="@drawable/ic_thumb_up" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -116,6 +117,7 @@
|
|||||||
android:id="@+id/image_dislike_icon"
|
android:id="@+id/image_dislike_icon"
|
||||||
android:layout_width="18dp"
|
android:layout_width="18dp"
|
||||||
android:layout_height="18dp"
|
android:layout_height="18dp"
|
||||||
|
android:contentDescription="@string/cd_image_dislike_icon"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginTop="2dp"
|
android:layout_marginTop="2dp"
|
||||||
app:srcCompat="@drawable/ic_thumb_down" />
|
app:srcCompat="@drawable/ic_thumb_down" />
|
||||||
@ -134,6 +136,7 @@
|
|||||||
android:id="@+id/button_replies"
|
android:id="@+id/button_replies"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/cd_button_replies"
|
||||||
app:pillIcon="@drawable/ic_forum"
|
app:pillIcon="@drawable/ic_forum"
|
||||||
app:pillText="55 Replies"
|
app:pillText="55 Replies"
|
||||||
android:layout_marginStart="15dp" />
|
android:layout_marginStart="15dp" />
|
||||||
|
@ -91,6 +91,7 @@
|
|||||||
android:id="@+id/button_replies"
|
android:id="@+id/button_replies"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/cd_button_replies"
|
||||||
app:pillIcon="@drawable/ic_forum"
|
app:pillIcon="@drawable/ic_forum"
|
||||||
app:pillText="55 Replies"
|
app:pillText="55 Replies"
|
||||||
android:layout_marginStart="15dp" />
|
android:layout_marginStart="15dp" />
|
||||||
@ -112,6 +113,7 @@
|
|||||||
android:id="@+id/pill_text"
|
android:id="@+id/pill_text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_delete"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="13dp"
|
android:textSize="13dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
android:id="@+id/button_subscribe"
|
android:id="@+id/button_subscribe"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/cd_button_subscribe"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/text_channel_metadata"
|
app:layout_constraintTop_toBottomOf="@id/text_channel_metadata"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
@ -65,6 +66,7 @@
|
|||||||
android:id="@+id/platform_indicator"
|
android:id="@+id/platform_indicator"
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
android:layout_marginTop="18dp"
|
android:layout_marginTop="18dp"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
android:id="@+id/image_device"
|
android:id="@+id/image_device"
|
||||||
android:layout_width="25dp"
|
android:layout_width="25dp"
|
||||||
android:layout_height="25dp"
|
android:layout_height="25dp"
|
||||||
|
android:contentDescription="@string/cd_image_device"
|
||||||
app:srcCompat="@drawable/ic_chromecast"
|
app:srcCompat="@drawable/ic_chromecast"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
@ -63,6 +64,7 @@
|
|||||||
android:id="@+id/image_loader"
|
android:id="@+id/image_loader"
|
||||||
android:layout_width="25dp"
|
android:layout_width="25dp"
|
||||||
android:layout_height="25dp"
|
android:layout_height="25dp"
|
||||||
|
android:contentDescription="@string/cd_image_loader"
|
||||||
app:srcCompat="@drawable/ic_loader_animated"
|
app:srcCompat="@drawable/ic_loader_animated"
|
||||||
android:layout_marginEnd="8dp"/>
|
android:layout_marginEnd="8dp"/>
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
android:id="@+id/donation_author_image"
|
android:id="@+id/donation_author_image"
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
android:layout_height="20dp"
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@string/cd_donation_author_image"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
@ -173,6 +173,7 @@
|
|||||||
android:id="@+id/image_trash"
|
android:id="@+id/image_trash"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_delete"
|
||||||
app:srcCompat="@drawable/ic_trash_18dp"
|
app:srcCompat="@drawable/ic_trash_18dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
|
@ -45,7 +45,8 @@
|
|||||||
<com.futo.platformplayer.views.platform.PlatformIndicator
|
<com.futo.platformplayer.views.platform.PlatformIndicator
|
||||||
android:id="@+id/platform"
|
android:id="@+id/platform"
|
||||||
android:layout_width="25dp"
|
android:layout_width="25dp"
|
||||||
android:layout_height="25dp" />
|
android:layout_height="25dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -194,6 +194,7 @@
|
|||||||
android:id="@+id/creator_thumbnail"
|
android:id="@+id/creator_thumbnail"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="32dp"
|
android:layout_height="32dp"
|
||||||
|
android:contentDescription="@string/cd_creator_thumbnail"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
@ -272,6 +273,7 @@
|
|||||||
android:id="@+id/thumbnail_platform"
|
android:id="@+id/thumbnail_platform"
|
||||||
android:layout_width="25dp"
|
android:layout_width="25dp"
|
||||||
android:layout_height="25dp"
|
android:layout_height="25dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
tools:src="@drawable/ic_peertube"/>
|
tools:src="@drawable/ic_peertube"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -306,6 +306,7 @@
|
|||||||
android:id="@+id/thumbnail_platform"
|
android:id="@+id/thumbnail_platform"
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
android:layout_height="20dp"
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
android:layout_margin="4dp" />
|
android:layout_margin="4dp" />
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
android:id="@+id/image_drag_drop"
|
android:id="@+id/image_drag_drop"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_drag_drop"
|
||||||
app:srcCompat="@drawable/ic_dragdrop_white"
|
app:srcCompat="@drawable/ic_dragdrop_white"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
@ -116,6 +117,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
|
android:contentDescription="@string/cd_download_indicator"
|
||||||
app:srcCompat="@drawable/download_for_offline" />
|
app:srcCompat="@drawable/download_for_offline" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
@ -174,6 +176,7 @@
|
|||||||
android:id="@+id/image_trash"
|
android:id="@+id/image_trash"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_delete"
|
||||||
app:srcCompat="@drawable/ic_trash_18dp"
|
app:srcCompat="@drawable/ic_trash_18dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
android:id="@+id/thumbnail_platform"
|
android:id="@+id/thumbnail_platform"
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
android:layout_height="20dp"
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
|
@ -108,6 +108,7 @@
|
|||||||
android:id="@+id/creator_thumbnail"
|
android:id="@+id/creator_thumbnail"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="32dp"
|
android:layout_height="32dp"
|
||||||
|
android:contentDescription="@string/cd_creator_thumbnail"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
@ -161,6 +162,7 @@
|
|||||||
android:id="@+id/thumbnail_platform"
|
android:id="@+id/thumbnail_platform"
|
||||||
android:layout_width="25dp"
|
android:layout_width="25dp"
|
||||||
android:layout_height="25dp"
|
android:layout_height="25dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
android:scaleType="centerInside" />
|
android:scaleType="centerInside" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -72,6 +72,7 @@
|
|||||||
android:id="@+id/button_trash"
|
android:id="@+id/button_trash"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_delete"
|
||||||
app:srcCompat="@drawable/ic_trash"
|
app:srcCompat="@drawable/ic_trash"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
|
@ -90,6 +90,7 @@
|
|||||||
android:id="@+id/platform_indicator"
|
android:id="@+id/platform_indicator"
|
||||||
android:layout_width="25dp"
|
android:layout_width="25dp"
|
||||||
android:layout_height="25dp"
|
android:layout_height="25dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
app:layout_constraintTop_toTopOf="@id/image_author_thumbnail"
|
app:layout_constraintTop_toTopOf="@id/image_author_thumbnail"
|
||||||
@ -157,6 +158,7 @@
|
|||||||
android:id="@+id/image_like_icon"
|
android:id="@+id/image_like_icon"
|
||||||
android:layout_width="18dp"
|
android:layout_width="18dp"
|
||||||
android:layout_height="18dp"
|
android:layout_height="18dp"
|
||||||
|
android:contentDescription="@string/cd_image_like_icon"
|
||||||
app:srcCompat="@drawable/ic_thumb_up" />
|
app:srcCompat="@drawable/ic_thumb_up" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -173,6 +175,7 @@
|
|||||||
android:id="@+id/image_dislike_icon"
|
android:id="@+id/image_dislike_icon"
|
||||||
android:layout_width="18dp"
|
android:layout_width="18dp"
|
||||||
android:layout_height="18dp"
|
android:layout_height="18dp"
|
||||||
|
android:contentDescription="@string/cd_image_dislike_icon"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginTop="2dp"
|
android:layout_marginTop="2dp"
|
||||||
app:srcCompat="@drawable/ic_thumb_down" />
|
app:srcCompat="@drawable/ic_thumb_down" />
|
||||||
@ -202,6 +205,7 @@
|
|||||||
android:id="@+id/image_comments"
|
android:id="@+id/image_comments"
|
||||||
android:layout_width="18dp"
|
android:layout_width="18dp"
|
||||||
android:layout_height="18dp"
|
android:layout_height="18dp"
|
||||||
|
android:contentDescription="@string/Replies"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginTop="2dp"
|
android:layout_marginTop="2dp"
|
||||||
app:srcCompat="@drawable/ic_forum" />
|
app:srcCompat="@drawable/ic_forum" />
|
||||||
|
@ -90,6 +90,7 @@
|
|||||||
android:id="@+id/platform_indicator"
|
android:id="@+id/platform_indicator"
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
android:layout_height="20dp"
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
tools:src="@drawable/ic_peertube"
|
tools:src="@drawable/ic_peertube"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
android:id="@+id/image_source"
|
android:id="@+id/image_source"
|
||||||
android:layout_width="35dp"
|
android:layout_width="35dp"
|
||||||
android:layout_height="35dp"
|
android:layout_height="35dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
app:srcCompat="@drawable/ic_peertube"
|
app:srcCompat="@drawable/ic_peertube"
|
||||||
android:scaleType="fitCenter" />
|
android:scaleType="fitCenter" />
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
android:id="@+id/image_source"
|
android:id="@+id/image_source"
|
||||||
android:layout_width="35dp"
|
android:layout_width="35dp"
|
||||||
android:layout_height="35dp"
|
android:layout_height="35dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
app:srcCompat="@drawable/ic_peertube"
|
app:srcCompat="@drawable/ic_peertube"
|
||||||
android:scaleType="fitCenter" />
|
android:scaleType="fitCenter" />
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
android:id="@+id/image_drag_drop"
|
android:id="@+id/image_drag_drop"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_drag_drop"
|
||||||
app:srcCompat="@drawable/ic_dragdrop_white"
|
app:srcCompat="@drawable/ic_dragdrop_white"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
@ -34,6 +35,7 @@
|
|||||||
android:id="@+id/image_source"
|
android:id="@+id/image_source"
|
||||||
android:layout_width="35dp"
|
android:layout_width="35dp"
|
||||||
android:layout_height="35dp"
|
android:layout_height="35dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
app:srcCompat="@drawable/ic_peertube"
|
app:srcCompat="@drawable/ic_peertube"
|
||||||
android:scaleType="fitCenter" />
|
android:scaleType="fitCenter" />
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
android:id="@+id/creator_thumbnail"
|
android:id="@+id/creator_thumbnail"
|
||||||
android:layout_width="46dp"
|
android:layout_width="46dp"
|
||||||
android:layout_height="46dp"
|
android:layout_height="46dp"
|
||||||
|
android:contentDescription="@string/cd_creator_thumbnail"
|
||||||
android:layout_marginStart="20dp"/>
|
android:layout_marginStart="20dp"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@ -42,7 +43,8 @@
|
|||||||
<com.futo.platformplayer.views.platform.PlatformIndicator
|
<com.futo.platformplayer.views.platform.PlatformIndicator
|
||||||
android:id="@+id/platform"
|
android:id="@+id/platform"
|
||||||
android:layout_width="25dp"
|
android:layout_width="25dp"
|
||||||
android:layout_height="25dp" />
|
android:layout_height="25dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator" />
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_meta"
|
android:id="@+id/text_meta"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -60,6 +62,7 @@
|
|||||||
android:id="@+id/button_settings"
|
android:id="@+id/button_settings"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_settings"
|
||||||
app:srcCompat="@drawable/ic_settings"
|
app:srcCompat="@drawable/ic_settings"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:paddingStart="5dp"
|
android:paddingStart="5dp"
|
||||||
@ -70,6 +73,7 @@
|
|||||||
android:id="@+id/button_trash"
|
android:id="@+id/button_trash"
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_delete"
|
||||||
app:srcCompat="@drawable/ic_trash"
|
app:srcCompat="@drawable/ic_trash"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:paddingStart="5dp"
|
android:paddingStart="5dp"
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
android:id="@+id/thumb"
|
android:id="@+id/thumb"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_drag_drop"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
@ -25,6 +26,7 @@
|
|||||||
android:id="@+id/image"
|
android:id="@+id/image"
|
||||||
android:layout_width="75dp"
|
android:layout_width="75dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_image_group"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toRightOf="@id/thumb"
|
app:layout_constraintLeft_toRightOf="@id/thumb"
|
||||||
@ -77,6 +79,7 @@
|
|||||||
android:id="@+id/button_trash"
|
android:id="@+id/button_trash"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_delete"
|
||||||
android:layout_marginRight="10dp"
|
android:layout_marginRight="10dp"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
android:id="@+id/image_drag_drop"
|
android:id="@+id/image_drag_drop"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_drag_drop"
|
||||||
app:srcCompat="@drawable/ic_dragdrop_white"
|
app:srcCompat="@drawable/ic_dragdrop_white"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
|
@ -134,6 +134,7 @@
|
|||||||
android:id="@+id/creator_thumbnail"
|
android:id="@+id/creator_thumbnail"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="32dp"
|
android:layout_height="32dp"
|
||||||
|
android:contentDescription="@string/cd_creator_thumbnail"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
@ -205,6 +206,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_download"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
app:srcCompat="@drawable/download_for_offline" />
|
app:srcCompat="@drawable/download_for_offline" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
@ -213,6 +215,7 @@
|
|||||||
android:id="@+id/thumbnail_platform"
|
android:id="@+id/thumbnail_platform"
|
||||||
android:layout_width="25dp"
|
android:layout_width="25dp"
|
||||||
android:layout_height="25dp"
|
android:layout_height="25dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
tools:src="@drawable/ic_peertube" />
|
tools:src="@drawable/ic_peertube" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -230,6 +233,7 @@
|
|||||||
android:id="@+id/button_add_to_watch_later"
|
android:id="@+id/button_add_to_watch_later"
|
||||||
android:layout_width="30dp"
|
android:layout_width="30dp"
|
||||||
android:layout_height="30dp"
|
android:layout_height="30dp"
|
||||||
|
android:contentDescription="@string/cd_button_add_to_watch_later"
|
||||||
android:layout_marginEnd="5dp"
|
android:layout_marginEnd="5dp"
|
||||||
android:background="@drawable/edit_text_background"
|
android:background="@drawable/edit_text_background"
|
||||||
app:srcCompat="@drawable/ic_clock_white" />
|
app:srcCompat="@drawable/ic_clock_white" />
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
android:id="@+id/thumbnail_platform_nested"
|
android:id="@+id/thumbnail_platform_nested"
|
||||||
android:layout_width="25dp"
|
android:layout_width="25dp"
|
||||||
android:layout_height="25dp"
|
android:layout_height="25dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
android:layout_margin="5dp"
|
android:layout_margin="5dp"
|
||||||
@ -173,6 +174,7 @@
|
|||||||
android:id="@+id/creator_thumbnail"
|
android:id="@+id/creator_thumbnail"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="32dp"
|
android:layout_height="32dp"
|
||||||
|
android:contentDescription="@string/cd_creator_thumbnail"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
@ -242,6 +244,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_button_download"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
app:srcCompat="@drawable/download_for_offline" />
|
app:srcCompat="@drawable/download_for_offline" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
@ -250,6 +253,7 @@
|
|||||||
android:id="@+id/thumbnail_platform"
|
android:id="@+id/thumbnail_platform"
|
||||||
android:layout_width="25dp"
|
android:layout_width="25dp"
|
||||||
android:layout_height="25dp"
|
android:layout_height="25dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
tools:src="@drawable/ic_peertube"/>
|
tools:src="@drawable/ic_peertube"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -267,6 +271,7 @@
|
|||||||
android:id="@+id/button_add_to_watch_later"
|
android:id="@+id/button_add_to_watch_later"
|
||||||
android:layout_width="30dp"
|
android:layout_width="30dp"
|
||||||
android:layout_height="30dp"
|
android:layout_height="30dp"
|
||||||
|
android:contentDescription="@string/cd_button_add_to_watch_later"
|
||||||
android:layout_marginEnd="5dp"
|
android:layout_marginEnd="5dp"
|
||||||
android:background="@drawable/edit_text_background"
|
android:background="@drawable/edit_text_background"
|
||||||
app:srcCompat="@drawable/ic_clock_white" />
|
app:srcCompat="@drawable/ic_clock_white" />
|
||||||
|
@ -104,6 +104,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_download_indicator"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
app:srcCompat="@drawable/download_for_offline" />
|
app:srcCompat="@drawable/download_for_offline" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
@ -112,6 +113,7 @@
|
|||||||
android:id="@+id/thumbnail_platform"
|
android:id="@+id/thumbnail_platform"
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
android:layout_height="20dp"
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
@ -188,6 +190,7 @@
|
|||||||
android:id="@+id/button_add_to_watch_later"
|
android:id="@+id/button_add_to_watch_later"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="27dp"
|
android:layout_height="27dp"
|
||||||
|
android:contentDescription="@string/cd_button_add_to_watch_later"
|
||||||
android:src="@drawable/ic_clock_white"
|
android:src="@drawable/ic_clock_white"
|
||||||
android:paddingTop="7dp"
|
android:paddingTop="7dp"
|
||||||
android:paddingBottom="6dp"
|
android:paddingBottom="6dp"
|
||||||
|
@ -104,6 +104,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/cd_download_indicator"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
app:srcCompat="@drawable/download_for_offline" />
|
app:srcCompat="@drawable/download_for_offline" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
@ -112,6 +113,7 @@
|
|||||||
android:id="@+id/thumbnail_platform"
|
android:id="@+id/thumbnail_platform"
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
android:layout_height="20dp"
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
@ -227,6 +229,7 @@
|
|||||||
android:id="@+id/button_add_to_watch_later"
|
android:id="@+id/button_add_to_watch_later"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="27dp"
|
android:layout_height="27dp"
|
||||||
|
android:contentDescription="@string/cd_button_add_to_watch_later"
|
||||||
android:src="@drawable/ic_clock_white"
|
android:src="@drawable/ic_clock_white"
|
||||||
android:paddingTop="7dp"
|
android:paddingTop="7dp"
|
||||||
android:paddingBottom="6dp"
|
android:paddingBottom="6dp"
|
||||||
@ -320,6 +323,7 @@
|
|||||||
android:id="@+id/thumbnail_platform_nested"
|
android:id="@+id/thumbnail_platform_nested"
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
android:layout_height="20dp"
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@string/cd_platform_indicator"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
android:layout_margin="4dp" />
|
android:layout_margin="4dp" />
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
android:id="@+id/button_close"
|
android:id="@+id/button_close"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_close"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:layout_margin="7dp"
|
android:layout_margin="7dp"
|
||||||
@ -126,6 +127,7 @@
|
|||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_creator_thumbnail"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:layout_margin="20dp"
|
android:layout_margin="20dp"
|
||||||
@ -159,6 +161,7 @@
|
|||||||
android:id="@+id/donation_amount"
|
android:id="@+id/donation_amount"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/cd_donation_amount"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
tools:text="$100" />
|
tools:text="$100" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -229,6 +232,7 @@
|
|||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_creator_thumbnail"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
android:layout_marginStart="-20dp"
|
android:layout_marginStart="-20dp"
|
||||||
android:src="@drawable/placeholder_profile" />
|
android:src="@drawable/placeholder_profile" />
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
android:id="@+id/button_close"
|
android:id="@+id/button_close"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_close"
|
||||||
android:padding="5dp"
|
android:padding="5dp"
|
||||||
android:src="@drawable/ic_close" />
|
android:src="@drawable/ic_close" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
android:id="@+id/thumbnail_player_unmute"
|
android:id="@+id/thumbnail_player_unmute"
|
||||||
android:layout_width="34dp"
|
android:layout_width="34dp"
|
||||||
android:layout_height="34dp"
|
android:layout_height="34dp"
|
||||||
|
android:contentDescription="@string/cd_thumbnail_player_unmute"
|
||||||
android:padding="7dp"
|
android:padding="7dp"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
android:id="@+id/button_minimize"
|
android:id="@+id/button_minimize"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_minimize"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:padding="3dp"
|
android:padding="3dp"
|
||||||
@ -33,6 +34,7 @@
|
|||||||
android:id="@+id/button_autoplay"
|
android:id="@+id/button_autoplay"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_autoplay"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
@ -41,6 +43,7 @@
|
|||||||
android:id="@+id/button_cast"
|
android:id="@+id/button_cast"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_cast_button"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
@ -49,6 +52,7 @@
|
|||||||
android:id="@+id/button_rotate_lock"
|
android:id="@+id/button_rotate_lock"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_rotate_lock"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
@ -57,6 +61,7 @@
|
|||||||
android:id="@+id/button_loop"
|
android:id="@+id/button_loop"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_loop"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
@ -65,6 +70,7 @@
|
|||||||
android:id="@+id/button_settings"
|
android:id="@+id/button_settings"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_settings"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
@ -75,6 +81,7 @@
|
|||||||
android:id="@+id/button_previous"
|
android:id="@+id/button_previous"
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
|
android:contentDescription="@string/cd_button_previous"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:layout_marginRight="40dp"
|
android:layout_marginRight="40dp"
|
||||||
@ -118,6 +125,7 @@
|
|||||||
android:id="@+id/button_next"
|
android:id="@+id/button_next"
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
|
android:contentDescription="@string/cd_button_next"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:padding="5dp"
|
android:padding="5dp"
|
||||||
@ -131,6 +139,7 @@
|
|||||||
android:id="@+id/button_fullscreen"
|
android:id="@+id/button_fullscreen"
|
||||||
android:layout_width="55dp"
|
android:layout_width="55dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_fullscreen"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
app:srcCompat="@drawable/ic_expand"
|
app:srcCompat="@drawable/ic_expand"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
android:id="@+id/button_minimize"
|
android:id="@+id/button_minimize"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_minimize"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
@ -61,6 +62,7 @@
|
|||||||
android:id="@+id/button_autoplay"
|
android:id="@+id/button_autoplay"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_autoplay"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
@ -69,6 +71,7 @@
|
|||||||
android:id="@+id/button_cast"
|
android:id="@+id/button_cast"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_cast_button"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
@ -77,6 +80,7 @@
|
|||||||
android:id="@+id/button_rotate_lock"
|
android:id="@+id/button_rotate_lock"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_rotate_lock"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
@ -85,6 +89,7 @@
|
|||||||
android:id="@+id/button_loop"
|
android:id="@+id/button_loop"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_loop"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
@ -93,6 +98,7 @@
|
|||||||
android:id="@+id/button_settings"
|
android:id="@+id/button_settings"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
android:contentDescription="@string/cd_button_settings"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
@ -103,6 +109,7 @@
|
|||||||
android:id="@+id/button_previous"
|
android:id="@+id/button_previous"
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
|
android:contentDescription="@string/cd_button_previous"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:layout_marginRight="40dp"
|
android:layout_marginRight="40dp"
|
||||||
@ -146,6 +153,7 @@
|
|||||||
android:id="@+id/button_next"
|
android:id="@+id/button_next"
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
|
android:contentDescription="@string/cd_button_next"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:padding="5dp"
|
android:padding="5dp"
|
||||||
@ -159,6 +167,7 @@
|
|||||||
android:id="@+id/button_fullscreen"
|
android:id="@+id/button_fullscreen"
|
||||||
android:layout_width="55dp"
|
android:layout_width="55dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/cd_button_fullscreen"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
app:srcCompat="@drawable/ic_expand"
|
app:srcCompat="@drawable/ic_expand"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
@ -727,7 +727,7 @@
|
|||||||
<string name="not_yet_available_retrying_in_time_s">Not yet available, retrying in {time}s</string>
|
<string name="not_yet_available_retrying_in_time_s">Not yet available, retrying in {time}s</string>
|
||||||
<string name="failed_to_retry_for_live_stream">Failed to retry for live stream</string>
|
<string name="failed_to_retry_for_live_stream">Failed to retry for live stream</string>
|
||||||
<string name="this_app_is_in_development_please_submit_bug_reports_and_understand_that_many_features_are_incomplete">This app is in development. Please submit bug reports and understand that many features are incomplete.</string>
|
<string name="this_app_is_in_development_please_submit_bug_reports_and_understand_that_many_features_are_incomplete">This app is in development. Please submit bug reports and understand that many features are incomplete.</string>
|
||||||
<string name="please_use_at_least_3_characters">Please use at least 3 characters</string>
|
<string name="please_use_at_least_1_character">Please use at least 1 character</string>
|
||||||
<string name="are_you_sure_you_want_to_delete_this_video">Are you sure you want to delete this video?</string>
|
<string name="are_you_sure_you_want_to_delete_this_video">Are you sure you want to delete this video?</string>
|
||||||
<string name="tap_to_open">Tap to open</string>
|
<string name="tap_to_open">Tap to open</string>
|
||||||
<string name="update_available_exclamation">Update available!</string>
|
<string name="update_available_exclamation">Update available!</string>
|
||||||
@ -811,6 +811,58 @@
|
|||||||
<string name="scroll_to_top">Scroll to top</string>
|
<string name="scroll_to_top">Scroll to top</string>
|
||||||
<string name="disable_battery_optimization">Disable Battery Optimization</string>
|
<string name="disable_battery_optimization">Disable Battery Optimization</string>
|
||||||
<string name="click_to_go_to_battery_optimization_settings_disabling_battery_optimization_will_prevent_the_os_from_killing_media_sessions">Click to go to battery optimization settings. Disabling battery optimization will prevent the OS from killing media sessions.</string>
|
<string name="click_to_go_to_battery_optimization_settings_disabling_battery_optimization_will_prevent_the_os_from_killing_media_sessions">Click to go to battery optimization settings. Disabling battery optimization will prevent the OS from killing media sessions.</string>
|
||||||
|
<string name="contribute_personal_subscriptions_list">Contribute Personal Subscriptions List</string>
|
||||||
|
<string name="contribute_personal_subscriptions_list_description">\nWould you liked to contribute your current creator subscriptions list to FUTO?\n\nThe data will be handled according to the Grayjay privacy policy. That is the list will be anonymized and stored without any reference to whomever the list of creators belonged to.\n\nThe intention is for Grayjay and FUTO to use these data to build a cross platform creator recommendation system to make it easier to find new creators you might like from within Grayjay.</string>
|
||||||
|
<string name="cd_cast_button">Cast button</string>
|
||||||
|
<string name="cd_incognito_button">Incognito button</string>
|
||||||
|
<string name="cd_creator_thumbnail">Creator thumbnail</string>
|
||||||
|
<string name="cd_button_clear_search">Clear search</string>
|
||||||
|
<string name="cd_button_search">Search</string>
|
||||||
|
<string name="cd_search_icon">Search icon</string>
|
||||||
|
<string name="cd_button_back">Back button</string>
|
||||||
|
<string name="cd_app_icon">App icon</string>
|
||||||
|
<string name="cd_icon_history">History icon</string>
|
||||||
|
<string name="cd_button_create_playlist">Create playlist</string>
|
||||||
|
<string name="cd_button_share">Share</string>
|
||||||
|
<string name="cd_button_filter">Filter</string>
|
||||||
|
<string name="cd_button_delete">Delete</string>
|
||||||
|
<string name="cd_button_settings">Settings</string>
|
||||||
|
<string name="cd_image_group">Group image</string>
|
||||||
|
<string name="cd_button_edit">Edit</string>
|
||||||
|
<string name="cd_button_download">Download</string>
|
||||||
|
<string name="cd_minimize_close">Close</string>
|
||||||
|
<string name="cd_minimize_pause">Pause</string>
|
||||||
|
<string name="cd_minimize_play">Play</string>
|
||||||
|
<string name="cd_donation_amount">Donation amount</string>
|
||||||
|
<string name="cd_button_replies">Replies</string>
|
||||||
|
<string name="cd_image_like_icon">Like</string>
|
||||||
|
<string name="cd_image_dislike_icon">Dislike</string>
|
||||||
|
<string name="cd_button_subscribe">Subscribe</string>
|
||||||
|
<string name="cd_platform_indicator">Platform indicator</string>
|
||||||
|
<string name="cd_image_device">Device icon</string>
|
||||||
|
<string name="cd_image_loader">Loader</string>
|
||||||
|
<string name="cd_donation_author_image">Donation author image</string>
|
||||||
|
<string name="cd_edit_image">Edit image</string>
|
||||||
|
<string name="cd_button_add">Add</string>
|
||||||
|
<string name="cd_download_indicator">Download indicator</string>
|
||||||
|
<string name="cd_drag_drop">Drag and drop</string>
|
||||||
|
<string name="cd_button_add_to_watch_later">Add to Watch Later</string>
|
||||||
|
<string name="cd_button_close">Close</string>
|
||||||
|
<string name="cd_thumbnail_player_unmute">Unmute</string>
|
||||||
|
<string name="cd_button_minimize">Minimize</string>
|
||||||
|
<string name="cd_button_rotate_lock">Lock rotation</string>
|
||||||
|
<string name="cd_button_loop">Loop</string>
|
||||||
|
<string name="cd_button_previous">Previous</string>
|
||||||
|
<string name="cd_button_next">Next</string>
|
||||||
|
<string name="cd_button_fullscreen">Fullscreen</string>
|
||||||
|
<string name="cd_button_autoplay">Autoplay</string>
|
||||||
|
<string name="cd_update_spinner">Update spinner</string>
|
||||||
|
<string name="cd_button_play">Play</string>
|
||||||
|
<string name="cd_button_pause">Pause</string>
|
||||||
|
<string name="cd_button_stop">Stop</string>
|
||||||
|
<string name="cd_button_scan_qr">Scan QR code</string>
|
||||||
|
<string name="cd_button_help">Help</string>
|
||||||
|
<string name="cd_image_polycentric">Change Polycentric profile picture</string>
|
||||||
<string-array name="home_screen_array">
|
<string-array name="home_screen_array">
|
||||||
<item>Recommendations</item>
|
<item>Recommendations</item>
|
||||||
<item>Subscriptions</item>
|
<item>Subscriptions</item>
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 58ea77229dcdb5c9ce8f1bd642baf29486d0bf21
|
Subproject commit 543a727d781fe5780fd0e8f20d53f6a53b285446
|
@ -1 +1 @@
|
|||||||
Subproject commit 80c9b4d3b48739170b40b313be930329dcc59fe4
|
Subproject commit 0ce91be276681ab82d26f9471523beab6b2a0a00
|
@ -524,8 +524,8 @@ class NoiseProtocolTest {
|
|||||||
println("Initiator handshake complete")
|
println("Initiator handshake complete")
|
||||||
handshakeLatch.countDown() // Handshake complete for initiator
|
handshakeLatch.countDown() // Handshake complete for initiator
|
||||||
},
|
},
|
||||||
onData = { session, opcode, data ->
|
onData = { session, opcode, subOpcode, data ->
|
||||||
println("Initiator received: Opcode $opcode, Data Length: ${data.remaining()}")
|
println("Initiator received: Opcode: $opcode, SubOpcode: $subOpcode, Data Length: ${data.remaining()}")
|
||||||
|
|
||||||
when (data.remaining()) {
|
when (data.remaining()) {
|
||||||
randomBytesExactlyOnePacket.remaining() -> {
|
randomBytesExactlyOnePacket.remaining() -> {
|
||||||
@ -556,8 +556,8 @@ class NoiseProtocolTest {
|
|||||||
println("Responder handshake complete")
|
println("Responder handshake complete")
|
||||||
handshakeLatch.countDown() // Handshake complete for responder
|
handshakeLatch.countDown() // Handshake complete for responder
|
||||||
},
|
},
|
||||||
onData = { session, opcode, data ->
|
onData = { session, opcode, subOpcode, data ->
|
||||||
println("Responder received: Opcode $opcode, Data Length: ${data.remaining()}")
|
println("Responder received: Opcode $opcode, SubOpcode $subOpcode, Data Length: ${data.remaining()}")
|
||||||
|
|
||||||
when (data.remaining()) {
|
when (data.remaining()) {
|
||||||
randomBytesExactlyOnePacket.remaining() -> {
|
randomBytesExactlyOnePacket.remaining() -> {
|
||||||
@ -590,12 +590,12 @@ class NoiseProtocolTest {
|
|||||||
responderSession.send(SyncSocketSession.Opcode.PONG.value)
|
responderSession.send(SyncSocketSession.Opcode.PONG.value)
|
||||||
|
|
||||||
// Test data transfer
|
// Test data transfer
|
||||||
responderSession.send(SyncSocketSession.Opcode.NOTIFY_AUTHORIZED.value, randomBytesExactlyOnePacket)
|
responderSession.send(SyncSocketSession.Opcode.DATA.value, 0u, randomBytesExactlyOnePacket)
|
||||||
initiatorSession.send(SyncSocketSession.Opcode.NOTIFY_AUTHORIZED.value, randomBytes)
|
initiatorSession.send(SyncSocketSession.Opcode.DATA.value, 1u, randomBytes)
|
||||||
|
|
||||||
// Send large data to test stream handling
|
// Send large data to test stream handling
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
responderSession.send(SyncSocketSession.Opcode.NOTIFY_AUTHORIZED.value, randomBytesBig)
|
responderSession.send(SyncSocketSession.Opcode.DATA.value, 0u, randomBytesBig)
|
||||||
println("Sent 10MB in ${System.currentTimeMillis() - start}ms")
|
println("Sent 10MB in ${System.currentTimeMillis() - start}ms")
|
||||||
|
|
||||||
// Wait for a brief period to simulate delay and allow communication
|
// Wait for a brief period to simulate delay and allow communication
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 58ea77229dcdb5c9ce8f1bd642baf29486d0bf21
|
Subproject commit 543a727d781fe5780fd0e8f20d53f6a53b285446
|
@ -1 +1 @@
|
|||||||
Subproject commit 80c9b4d3b48739170b40b313be930329dcc59fe4
|
Subproject commit 0ce91be276681ab82d26f9471523beab6b2a0a00
|
Loading…
x
Reference in New Issue
Block a user