mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-20 03:24:50 +00:00
appToast system, VideoToOpen changed
This commit is contained in:
parent
8488706ff9
commit
d86a997a88
13 changed files with 237 additions and 94 deletions
|
@ -37,6 +37,7 @@ import com.futo.platformplayer.logging.Logger
|
|||
import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.states.StateBackup
|
||||
import com.futo.platformplayer.stores.v2.ManagedStore
|
||||
import com.futo.platformplayer.views.ToastView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -398,13 +399,28 @@ class UIDialogs {
|
|||
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
|
||||
try {
|
||||
StateApp.withContext {
|
||||
Toast.makeText(it, text, if (long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT).show();
|
||||
toast(it, text, long);
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, "Failed to show toast.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
fun appToast(text: String, long: Boolean = false) {
|
||||
appToast(ToastView.Toast(text, long))
|
||||
}
|
||||
fun appToastError(text: String, long: Boolean) {
|
||||
StateApp.withContext {
|
||||
appToast(ToastView.Toast(text, long, it.getColor(R.color.pastel_red)));
|
||||
};
|
||||
}
|
||||
fun appToast(toast: ToastView.Toast) {
|
||||
StateApp.withContext {
|
||||
if(it is MainActivity) {
|
||||
it.showAppToast(toast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showClickableToast(context: Context, text: String, onClick: () -> Unit, isLongDuration: Boolean = false) {
|
||||
//TODO: Is not actually clickable...
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.futo.platformplayer.activities
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
|
@ -24,6 +23,7 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentContainerView
|
||||
import androidx.lifecycle.Lifecycle
|
||||
|
@ -45,6 +45,7 @@ import com.futo.platformplayer.states.*
|
|||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.SubscriptionStorage
|
||||
import com.futo.platformplayer.stores.v2.ManagedStore
|
||||
import com.futo.platformplayer.views.ToastView
|
||||
import com.google.gson.JsonParser
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import kotlinx.coroutines.*
|
||||
|
@ -54,6 +55,7 @@ import java.io.PrintWriter
|
|||
import java.io.StringWriter
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
|
||||
|
@ -65,6 +67,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||
lateinit var rootView : MotionLayout;
|
||||
|
||||
private lateinit var _overlayContainer: FrameLayout;
|
||||
private lateinit var _toastView: ToastView;
|
||||
|
||||
//Segment Containers
|
||||
private lateinit var _fragContainerTopBar: FragmentContainerView;
|
||||
|
@ -207,7 +210,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||
_fragContainerVideoDetail = findViewById(R.id.fragment_overlay);
|
||||
_fragContainerOverlay = findViewById(R.id.fragment_overlay_container);
|
||||
_overlayContainer = findViewById(R.id.overlay_container);
|
||||
//_overlayContainer.visibility = View.GONE;
|
||||
_toastView = findViewById(R.id.toast_view);
|
||||
|
||||
//Initialize fragments
|
||||
|
||||
|
@ -478,21 +481,6 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||
}
|
||||
|
||||
_isVisible = true;
|
||||
val videoToOpen = StateSaved.instance.videoToOpen;
|
||||
|
||||
if (_wasStopped) {
|
||||
_wasStopped = false;
|
||||
|
||||
if (videoToOpen != null && _fragVideoDetail.state == VideoDetailFragment.State.CLOSED) {
|
||||
Logger.i(TAG, "onResume videoToOpen=$videoToOpen");
|
||||
if (StatePlatform.instance.hasEnabledVideoClient(videoToOpen.url)) {
|
||||
navigate(_fragVideoDetail, UrlVideoWithTime(videoToOpen.url, videoToOpen.timeSeconds, false));
|
||||
_fragVideoDetail.maximizeVideoDetail(true);
|
||||
}
|
||||
|
||||
StateSaved.instance.setVideoToOpenNonBlocking(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
@ -864,7 +852,6 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||
_orientationManager.disable();
|
||||
|
||||
StateApp.instance.mainAppDestroyed(this);
|
||||
StateSaved.instance.setVideoToOpenBlocking(null);
|
||||
}
|
||||
|
||||
inline fun <reified T> isFragmentActive(): Boolean {
|
||||
|
@ -1052,6 +1039,43 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||
}
|
||||
}
|
||||
|
||||
private val _toastQueue = ConcurrentLinkedQueue<ToastView.Toast>();
|
||||
private var _toastJob: Job? = null;
|
||||
fun showAppToast(toast: ToastView.Toast) {
|
||||
synchronized(_toastQueue) {
|
||||
_toastQueue.add(toast);
|
||||
if(_toastJob?.isActive != true)
|
||||
_toastJob = lifecycleScope.launch(Dispatchers.Default) {
|
||||
launchAppToastJob();
|
||||
};
|
||||
}
|
||||
}
|
||||
private suspend fun launchAppToastJob() {
|
||||
Logger.i(TAG, "Starting appToast loop");
|
||||
while(!_toastQueue.isEmpty()) {
|
||||
val toast = _toastQueue.poll() ?: continue;
|
||||
Logger.i(TAG, "Showing next toast (${toast.msg})");
|
||||
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
if (!_toastView.isVisible) {
|
||||
Logger.i(TAG, "First showing toast");
|
||||
_toastView.setToast(toast);
|
||||
_toastView.show(true);
|
||||
} else {
|
||||
_toastView.setToastAnimated(toast);
|
||||
}
|
||||
}
|
||||
if(toast.long)
|
||||
delay(5000);
|
||||
else
|
||||
delay(3000);
|
||||
}
|
||||
Logger.i(TAG, "Ending appToast loop");
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
_toastView.hide(true) {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//TODO: Only calls last handler due to missing request codes on ActivityResultLaunchers.
|
||||
|
|
|
@ -10,6 +10,7 @@ import androidx.lifecycle.lifecycleScope
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.futo.platformplayer.*
|
||||
import com.futo.platformplayer.activities.MainActivity
|
||||
import com.futo.platformplayer.api.media.IPlatformClient
|
||||
import com.futo.platformplayer.api.media.models.contents.ContentType
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
|
@ -427,7 +428,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||
context?.let {
|
||||
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||
try {
|
||||
if (exs.size <= 8) {
|
||||
if (exs.size <= 3) {
|
||||
for (ex in exs) {
|
||||
var toShow = ex;
|
||||
var channel: String? = null;
|
||||
|
@ -437,12 +438,11 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||
}
|
||||
Logger.e(TAG, "Channel [${channel}] failed", ex);
|
||||
if (toShow is PluginException)
|
||||
UIDialogs.toast(
|
||||
it,
|
||||
UIDialogs.appToast(
|
||||
context.getString(R.string.plugin_pluginname_failed_message).replace("{pluginName}", toShow.config.name).replace("{message}", toShow.message ?: "")
|
||||
);
|
||||
else
|
||||
UIDialogs.toast(it, ex.message ?: "");
|
||||
UIDialogs.appToast(ex.message ?: "");
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -453,7 +453,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||
.map { it!! }
|
||||
.toList();
|
||||
for(distinctPluginFail in failedPlugins)
|
||||
UIDialogs.toast(it, context.getString(R.string.plugin_pluginname_failed_message).replace("{pluginName}", distinctPluginFail.config.name).replace("{message}", distinctPluginFail.message ?: ""));
|
||||
UIDialogs.appToast(context.getString(R.string.plugin_pluginname_failed_message).replace("{pluginName}", distinctPluginFail.config.name).replace("{message}", distinctPluginFail.message ?: ""));
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, "Failed to handle exceptions", e)
|
||||
|
|
|
@ -25,8 +25,6 @@ import com.futo.platformplayer.logging.Logger
|
|||
import com.futo.platformplayer.models.PlatformVideoWithTime
|
||||
import com.futo.platformplayer.models.UrlVideoWithTime
|
||||
import com.futo.platformplayer.states.StatePlayer
|
||||
import com.futo.platformplayer.states.StateSaved
|
||||
import com.futo.platformplayer.states.VideoToOpen
|
||||
import com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout
|
||||
|
||||
class VideoDetailFragment : MainFragment {
|
||||
|
@ -372,11 +370,6 @@ class VideoDetailFragment : MainFragment {
|
|||
|
||||
Logger.v(TAG, "shouldStop: $shouldStop");
|
||||
if(shouldStop) {
|
||||
_viewDetail?.let {
|
||||
val v = it.video ?: return@let;
|
||||
StateSaved.instance.setVideoToOpenBlocking(VideoToOpen(v.url, (it.lastPositionMilliseconds / 1000.0f).toLong()));
|
||||
}
|
||||
|
||||
_viewDetail?.onStop();
|
||||
StateCasting.instance.onStop();
|
||||
Logger.v(TAG, "called onStop() shouldStop: $shouldStop");
|
||||
|
|
|
@ -149,6 +149,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import userpackage.Protocol
|
||||
import java.time.OffsetDateTime
|
||||
|
@ -853,14 +855,19 @@ class VideoDetailView : ConstraintLayout {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val _historyIndexLock = Mutex(false);
|
||||
suspend fun getHistoryIndex(video: IPlatformVideo): DBHistory.Index = withContext(Dispatchers.IO){
|
||||
val current = _historyIndex;
|
||||
if(current == null || current.url != video.url) {
|
||||
val index = StateHistory.instance.getHistoryByVideo(video, true)!!;
|
||||
_historyIndex = index;
|
||||
return@withContext index;
|
||||
_historyIndexLock.withLock {
|
||||
val current = _historyIndex;
|
||||
if(current == null || current.url != video.url) {
|
||||
val index = StateHistory.instance.getHistoryByVideo(video, true)!!;
|
||||
_historyIndex = index;
|
||||
return@withContext index;
|
||||
}
|
||||
return@withContext current;
|
||||
}
|
||||
return@withContext current;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1121,7 +1128,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
|
||||
switchContentView(_container_content_main);
|
||||
}
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
//@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun setVideoDetails(videoDetail: IPlatformVideoDetails, newVideo: Boolean = false) {
|
||||
Logger.i(TAG, "setVideoDetails (${videoDetail.name})")
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ class MediaPlaybackService : Service() {
|
|||
override fun onDestroy() {
|
||||
Logger.v(TAG, "onDestroy");
|
||||
_instance = null;
|
||||
MediaControlReceiver.onCloseReceived.emit();
|
||||
MediaControlReceiver.onPauseReceived.emit();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.app.Activity
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.Color
|
||||
import android.media.AudioManager
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
|
@ -38,6 +39,7 @@ import com.futo.platformplayer.receivers.AudioNoisyReceiver
|
|||
import com.futo.platformplayer.services.DownloadService
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.v2.ManagedStore
|
||||
import com.futo.platformplayer.views.ToastView
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
import java.time.OffsetDateTime
|
||||
|
@ -380,8 +382,6 @@ class StateApp {
|
|||
|
||||
Logger.i(TAG, "MainApp Starting: Initializing [Polycentric]");
|
||||
StatePolycentric.instance.load(context);
|
||||
Logger.i(TAG, "MainApp Starting: Initializing [Saved]");
|
||||
StateSaved.instance.load();
|
||||
|
||||
Logger.i(TAG, "MainApp Starting: Initializing [Connectivity]");
|
||||
displayMetrics = context.resources.displayMetrics;
|
||||
|
@ -581,6 +581,14 @@ class StateApp {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
UIDialogs.appToast("This is a test", false);
|
||||
UIDialogs.appToast("This is a test 2", false);
|
||||
UIDialogs.appToastError("This is a test 3 (Error)", false);
|
||||
UIDialogs.appToast(ToastView.Toast("This is a test 4, with title", false, Color.WHITE, "Test title"));
|
||||
UIDialogs.appToast("This is a test 5 Long text\nWith enters\nasdh asfh fds h rwe h fxh sdfh sdf h dsfh sdf hasdfhsdhg ads as", true);
|
||||
*/
|
||||
}
|
||||
|
||||
fun mainAppStartedWithExternalFiles(context: Context) {
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
package com.futo.platformplayer.states
|
||||
|
||||
import com.futo.platformplayer.api.media.Serializer
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.StringStorage
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
data class VideoToOpen(val url: String, val timeSeconds: Long);
|
||||
|
||||
class StateSaved {
|
||||
var videoToOpen: VideoToOpen? = null;
|
||||
|
||||
private val _videoToOpen = FragmentedStorage.get<StringStorage>("videoToOpen")
|
||||
|
||||
fun load() {
|
||||
val videoToOpenString = _videoToOpen.value;
|
||||
if (videoToOpenString.isNotEmpty()) {
|
||||
try {
|
||||
val v = Serializer.json.decodeFromString<VideoToOpen>(videoToOpenString);
|
||||
videoToOpen = v;
|
||||
} catch (e: Throwable) {
|
||||
Logger.w(TAG, "Failed to load video to open", e)
|
||||
}
|
||||
}
|
||||
|
||||
Logger.i(TAG, "loaded videoToOpen=$videoToOpen");
|
||||
}
|
||||
|
||||
fun setVideoToOpenNonBlocking(v: VideoToOpen? = null) {
|
||||
Logger.i(TAG, "set videoToOpen=$v");
|
||||
|
||||
videoToOpen = v;
|
||||
_videoToOpen.setAndSave(if (v != null) Serializer.json.encodeToString(v) else "");
|
||||
}
|
||||
|
||||
|
||||
fun setVideoToOpenBlocking(v: VideoToOpen? = null) {
|
||||
Logger.i(TAG, "set videoToOpen=$v");
|
||||
|
||||
videoToOpen = v;
|
||||
_videoToOpen.setAndSaveBlocking(if (v != null) Serializer.json.encodeToString(v) else "");
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "StateSaved"
|
||||
|
||||
val instance: StateSaved = StateSaved()
|
||||
}
|
||||
}
|
|
@ -55,7 +55,7 @@ abstract class SubscriptionsTaskFetchAlgorithm(
|
|||
val clientCacheCount = clientTasks.value.size - clientTaskCount;
|
||||
val limit = clientTasks.key.getSubscriptionRateLimit();
|
||||
if(clientCacheCount > 0 && clientTaskCount > 0 && limit != null && clientTaskCount >= limit && StateApp.instance.contextOrNull?.let { it is MainActivity && it.isFragmentActive<SubscriptionsFeedFragment>() } == true) {
|
||||
UIDialogs.toast("[${clientTasks.key.name}] only updating ${clientTaskCount} most urgent channels (rqs). (${clientCacheCount} cached)");
|
||||
UIDialogs.appToast("[${clientTasks.key.name}] only updating ${clientTaskCount} most urgent channels (rqs). (${clientCacheCount} cached)");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
91
app/src/main/java/com/futo/platformplayer/views/ToastView.kt
Normal file
91
app/src/main/java/com/futo/platformplayer/views/ToastView.kt
Normal file
|
@ -0,0 +1,91 @@
|
|||
package com.futo.platformplayer.views
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.dp
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
|
||||
class ToastView : LinearLayout {
|
||||
private val root: LinearLayout;
|
||||
private val title: TextView;
|
||||
private val text: TextView;
|
||||
init {
|
||||
inflate(context, R.layout.toast, this);
|
||||
root = findViewById(R.id.root);
|
||||
title = findViewById(R.id.title);
|
||||
text = findViewById(R.id.text);
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
|
||||
setToast(ToastView.Toast("", false))
|
||||
root.visibility = GONE;
|
||||
}
|
||||
|
||||
fun hide(animate: Boolean, onFinished: (()->Unit)? = null) {
|
||||
Logger.i("MainActivity", "Hiding toast");
|
||||
if(!animate) {
|
||||
root.visibility = GONE;
|
||||
alpha = 0f;
|
||||
onFinished?.invoke();
|
||||
}
|
||||
else {
|
||||
animate()
|
||||
.alpha(0f)
|
||||
.setDuration(700)
|
||||
.translationY(20.dp(context.resources).toFloat())
|
||||
.withEndAction { root.visibility = GONE; onFinished?.invoke(); }
|
||||
.start();
|
||||
}
|
||||
}
|
||||
fun show(animate: Boolean) {
|
||||
Logger.i("MainActivity", "Showing toast");
|
||||
if(!animate) {
|
||||
root.visibility = VISIBLE;
|
||||
alpha = 1f;
|
||||
}
|
||||
else {
|
||||
alpha = 0f;
|
||||
root.visibility = VISIBLE;
|
||||
translationY = 20.dp(context.resources).toFloat();
|
||||
animate()
|
||||
.alpha(1f)
|
||||
.setDuration(700)
|
||||
.translationY(0f)
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun setToast(toast: Toast) {
|
||||
if(toast.title.isNullOrEmpty())
|
||||
title.isVisible = false;
|
||||
else {
|
||||
title.text = toast.title;
|
||||
title.isVisible = true;
|
||||
}
|
||||
text.text = toast.msg;
|
||||
if(toast.color != null)
|
||||
text.setTextColor(toast.color);
|
||||
else
|
||||
text.setTextColor(Color.WHITE);
|
||||
}
|
||||
fun setToastAnimated(toast: Toast) {
|
||||
hide(true) {
|
||||
setToast(toast);
|
||||
show(true);
|
||||
};
|
||||
}
|
||||
|
||||
class Toast(
|
||||
val msg: String,
|
||||
val long: Boolean,
|
||||
val color: Int? = null,
|
||||
val title: String? = null
|
||||
);
|
||||
}
|
6
app/src/main/res/drawable/background_toast.xml
Normal file
6
app/src/main/res/drawable/background_toast.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#EE202020" />
|
||||
<corners android:radius="10dp" />
|
||||
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
|
||||
</shape>
|
|
@ -70,4 +70,13 @@
|
|||
android:visibility="gone"
|
||||
android:elevation="15dp">
|
||||
</FrameLayout>
|
||||
<com.futo.platformplayer.views.ToastView
|
||||
android:id="@+id/toast_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="50dp"
|
||||
android:elevation="30dp"
|
||||
app:layout_constraintLeft_toLeftOf="@id/fragment_main"
|
||||
app:layout_constraintRight_toRightOf="@id/fragment_main"
|
||||
app:layout_constraintBottom_toBottomOf="@id/fragment_main" />
|
||||
</androidx.constraintlayout.motion.widget.MotionLayout>
|
41
app/src/main/res/layout/toast.xml
Normal file
41
app/src/main/res/layout/toast.xml
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:toolNs="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/root"
|
||||
android:padding="10dp">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/background_toast"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:textColor="@color/white"
|
||||
toolNs:text="Some Title"
|
||||
android:fontFamily="@font/inter_bold"
|
||||
android:textSize="15dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/inter_light"
|
||||
toolNs:text="This is a test" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
Loading…
Add table
Reference in a new issue