fixed download logs text not being highlightable, fixed app not deleting some cached files, filed filenames being too short, fixed sometimes error output not being shown in terminal output, added keyboard focus when adding template in the terminal, made the loading of video player faster when you are cutting it
This commit is contained in:
parent
daac3fd241
commit
f88bfab70e
12 changed files with 92 additions and 106 deletions
|
|
@ -94,7 +94,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
|
|||
// System.currentTimeMillis(),
|
||||
// DateUtils.MINUTE_IN_MILLIS
|
||||
// )
|
||||
datetime.text = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.time * 1000L)
|
||||
datetime.text = SimpleDateFormat(android.text.format.DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.time * 1000L)
|
||||
|
||||
// BUTTON ----------------------------------
|
||||
val btn = card.findViewById<MaterialButton>(R.id.downloads_download_button_type)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.deniscerri.ytdlnis.work.DownloadWorker
|
|||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.sql.Date
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
|
@ -310,7 +311,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
).enqueue()
|
||||
}
|
||||
items.forEach {
|
||||
it.id = repository.checkIfReDownloadingErroredOrCancelled(it)
|
||||
it.id = withContext(Dispatchers.IO){
|
||||
repository.checkIfReDownloadingErroredOrCancelled(it)
|
||||
}
|
||||
if (it.id != 0L){
|
||||
repository.delete(it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,21 +107,20 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
|
|||
}
|
||||
|
||||
if (url.isBlank()) throw Exception("No Streaming URL found!")
|
||||
Log.e("aa", url.split("\n")[0])
|
||||
Log.e("aa", url.split("\n")[1])
|
||||
|
||||
val urls = url.split("\n")
|
||||
if (urls.size == 2){
|
||||
val audioSource: MediaSource =
|
||||
DefaultMediaSourceFactory(requireContext())
|
||||
.createMediaSource(fromUri(Uri.parse(urls[0])))
|
||||
val videoSource: MediaSource =
|
||||
DefaultMediaSourceFactory(requireContext())
|
||||
.createMediaSource(fromUri(Uri.parse(urls[1])))
|
||||
(player as ExoPlayer).setMediaSource(MergingMediaSource(videoSource, audioSource))
|
||||
}else{
|
||||
player.addMediaItem(fromUri(Uri.parse(urls[0])))
|
||||
}
|
||||
// val urls = url.split("\n")
|
||||
// if (urls.size == 2){
|
||||
// val audioSource: MediaSource =
|
||||
// DefaultMediaSourceFactory(requireContext())
|
||||
// .createMediaSource(fromUri(Uri.parse(urls[0])))
|
||||
// val videoSource: MediaSource =
|
||||
// DefaultMediaSourceFactory(requireContext())
|
||||
// .createMediaSource(fromUri(Uri.parse(urls[1])))
|
||||
// (player as ExoPlayer).setMediaSource(MergingMediaSource(videoSource, audioSource))
|
||||
// }else{
|
||||
// player.addMediaItem(fromUri(Uri.parse(urls[0])))
|
||||
// }
|
||||
player.addMediaItem(fromUri(Uri.parse(url)))
|
||||
|
||||
progress.visibility = View.GONE
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import android.util.Log
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
|
|
@ -17,11 +18,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.work.Data
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.*
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
|
|
@ -109,9 +106,7 @@ class TerminalActivity : AppCompatActivity() {
|
|||
input!!.requestFocus()
|
||||
hideCancelFab()
|
||||
}
|
||||
val line = work.progress.getString("output") ?: return@observe
|
||||
val id = work.progress.getInt("id", 0)
|
||||
if(id == 0) return@observe
|
||||
val line = work.progress.getString("output") ?: (work.outputData.getString("output") ?: return@observe)
|
||||
runOnUiThread {
|
||||
try {
|
||||
output!!.text = "${output!!.text}\n$line"
|
||||
|
|
@ -168,6 +163,11 @@ class TerminalActivity : AppCompatActivity() {
|
|||
item.setOnClickListener {
|
||||
input!!.text.insert(input!!.selectionStart, template.content + " ")
|
||||
bottomSheet.cancel()
|
||||
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
input!!.postDelayed({
|
||||
input!!.requestFocus()
|
||||
imm.showSoftInput(input!!, 0)
|
||||
}, 200)
|
||||
}
|
||||
linearLayout.addView(item)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ class DownloadLogActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
content = findViewById(R.id.content)
|
||||
content.setTextIsSelectable(true)
|
||||
contentScrollView = findViewById(R.id.content_scrollview)
|
||||
|
||||
copyLog = findViewById(R.id.copy_log)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
|
@ -135,15 +136,11 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
version!!.summary = BuildConfig.VERSION_NAME
|
||||
|
||||
val values = resources.getStringArray(R.array.language_values)
|
||||
if (Build.VERSION.SDK_INT >= 33 || Build.VERSION.SDK_INT < 24){
|
||||
language!!.isVisible = false
|
||||
}else{
|
||||
val entries = mutableListOf<String>()
|
||||
values.forEach {
|
||||
entries.add(Locale(it).getDisplayName(Locale(it)))
|
||||
}
|
||||
language!!.entries = entries.toTypedArray()
|
||||
val entries = mutableListOf<String>()
|
||||
values.forEach {
|
||||
entries.add(Locale(it).getDisplayName(Locale(it)))
|
||||
}
|
||||
language!!.entries = entries.toTypedArray()
|
||||
val lang = if (values.contains(Locale.getDefault().language)) Locale.getDefault().language else "en"
|
||||
editor.putString("app_language", lang)
|
||||
|
||||
|
|
@ -258,14 +255,14 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
|
||||
var cacheSize = requireContext().cacheDir.walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
var cacheSize = File(requireContext().cacheDir.absolutePath + "/downloads").walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
clearCache!!.summary = "(${fileUtil!!.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
|
||||
clearCache!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
if (activeDownloadCount == 0){
|
||||
requireContext().cacheDir.deleteRecursively()
|
||||
File(requireContext().cacheDir.absolutePath + "/downloads").deleteRecursively()
|
||||
Toast.makeText(requireContext(), getString(R.string.cache_cleared), Toast.LENGTH_SHORT).show()
|
||||
cacheSize = requireContext().cacheDir.walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
cacheSize = File(requireContext().cacheDir.absolutePath + "/downloads").walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
clearCache!!.summary = "(${fileUtil!!.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
|
||||
}else{
|
||||
Toast.makeText(requireContext(), getString(R.string.downloads_running_try_later), Toast.LENGTH_SHORT).show()
|
||||
|
|
|
|||
|
|
@ -132,13 +132,13 @@ class FileUtil() {
|
|||
}
|
||||
|
||||
}
|
||||
originDir.delete()
|
||||
originDir.deleteRecursively()
|
||||
return scanMedia(fileList, context)
|
||||
}
|
||||
|
||||
fun getLogFile(context: Context, item: DownloadItem) : File {
|
||||
val titleRegex = Regex("[^A-Za-z\\d ]")
|
||||
return File(context.filesDir.absolutePath + """/logs/${item.id} - ${titleRegex.replace(item.title, "")}##${item.type}##${item.format.format_id}.log""")
|
||||
return File(context.filesDir.absolutePath + """/logs/${item.id} - ${titleRegex.replace(item.title, "").take(150)}##${item.type}##${item.format.format_id}.log""")
|
||||
}
|
||||
fun getLogFileForTerminal(context: Context, command: String) : File {
|
||||
val titleRegex = Regex("[^A-Za-z\\d ]")
|
||||
|
|
|
|||
|
|
@ -630,23 +630,13 @@ class InfoUtil(context: Context) {
|
|||
}
|
||||
|
||||
fun getStreamingUrl(url: String) : String {
|
||||
try {
|
||||
return try {
|
||||
val request = YoutubeDLRequest(url)
|
||||
request.addOption("-j")
|
||||
request.addOption("--skip-download")
|
||||
request.addOption("-R", "1")
|
||||
request.addOption("--socket-timeout", "5")
|
||||
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
||||
val results: Array<String?> = try {
|
||||
val lineSeparator = System.getProperty("line.separator")
|
||||
youtubeDLResponse.out.split(lineSeparator!!).toTypedArray()
|
||||
} catch (e: Exception) {
|
||||
arrayOf(youtubeDLResponse.out)
|
||||
}
|
||||
val jsonObject = JSONObject(results[0]!!)
|
||||
return jsonObject.getString("urls")
|
||||
request.addOption("-f", "best")
|
||||
val youtubeDlInfo = YoutubeDL.getInstance().getInfo(request)
|
||||
youtubeDlInfo.url ?: ""
|
||||
} catch (e: Exception) {
|
||||
return ""
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ import android.os.Handler
|
|||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import androidx.work.*
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
|
|
@ -69,9 +66,9 @@ class DownloadWorker(
|
|||
val type = downloadItem.type
|
||||
val downloadLocation = downloadItem.downloadPath
|
||||
|
||||
val tempFileDir = File(context.cacheDir.absolutePath + "/" + downloadItem.id)
|
||||
val tempFileDir = File(context.cacheDir.absolutePath + "/downloads/" + downloadItem.id)
|
||||
tempFileDir.delete()
|
||||
tempFileDir.mkdir()
|
||||
tempFileDir.mkdirs()
|
||||
|
||||
val sharedPreferences = context.getSharedPreferences("root_preferences",
|
||||
Service.MODE_PRIVATE
|
||||
|
|
@ -111,10 +108,10 @@ class DownloadWorker(
|
|||
}
|
||||
|
||||
if(downloadItem.title.isNotBlank()){
|
||||
request.addCommands(listOf("--replace-in-metadata","title",".*.",downloadItem.title))
|
||||
request.addCommands(listOf("--replace-in-metadata","title",".*.",downloadItem.title.take(200)))
|
||||
}
|
||||
if (downloadItem.author.isNotBlank()){
|
||||
request.addCommands(listOf("--replace-in-metadata","uploader",".*.",downloadItem.author))
|
||||
request.addCommands(listOf("--replace-in-metadata","uploader",".*.",downloadItem.author.take(25)))
|
||||
}
|
||||
if (downloadItem.customFileNameTemplate.isBlank()) downloadItem.customFileNameTemplate = "%(uploader)s - %(title)s"
|
||||
|
||||
|
|
@ -127,8 +124,6 @@ class DownloadWorker(
|
|||
request.addOption("--restrict-filenames")
|
||||
}
|
||||
|
||||
request.addOption("--trim-filenames", 100)
|
||||
|
||||
when(type){
|
||||
DownloadViewModel.Type.audio -> {
|
||||
request.addOption("-x")
|
||||
|
|
@ -150,7 +145,7 @@ class DownloadWorker(
|
|||
request.addOption("--embed-thumbnail")
|
||||
request.addOption("--convert-thumbnails", "png")
|
||||
try {
|
||||
val config = File(context.cacheDir, "config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt")
|
||||
val config = File(context.cacheDir.absolutePath + "/downloads/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt")
|
||||
val configData =
|
||||
"--ppa \"ffmpeg: -c:v png -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
|
||||
config.writeText(configData)
|
||||
|
|
@ -177,6 +172,7 @@ class DownloadWorker(
|
|||
DownloadViewModel.Type.video -> {
|
||||
if (downloadItem.videoPreferences.addChapters) {
|
||||
request.addOption("--sponsorblock-mark", "all")
|
||||
request.addOption("--embed-chapters")
|
||||
}
|
||||
if (downloadItem.videoPreferences.embedSubs) {
|
||||
request.addOption("--embed-subs", "")
|
||||
|
|
@ -216,7 +212,7 @@ class DownloadWorker(
|
|||
DownloadViewModel.Type.command -> {
|
||||
request.addOption(
|
||||
"--config-locations",
|
||||
File(context.cacheDir, "config${System.currentTimeMillis()}.txt").apply {
|
||||
File(context.cacheDir.absolutePath + "/downloads/config${System.currentTimeMillis()}.txt").apply {
|
||||
writeText(downloadItem.format.format_note)
|
||||
}.absolutePath
|
||||
)
|
||||
|
|
@ -255,12 +251,13 @@ class DownloadWorker(
|
|||
}
|
||||
}.onSuccess {
|
||||
//move file from internal to set download directory
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads))
|
||||
var finalPath : String?
|
||||
try {
|
||||
finalPath = moveFile(tempFileDir.absoluteFile, downloadLocation){ progress ->
|
||||
setProgressAsync(workDataOf("progress" to progress))
|
||||
setProgressAsync(workDataOf("progress" to progress, "output" to "Moving file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads))
|
||||
}
|
||||
setProgressAsync(workDataOf("progress" to 1000, "output" to "Moved file to $finalPath", "id" to downloadItem.id, "log" to logDownloads))
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $finalPath", "id" to downloadItem.id, "log" to logDownloads))
|
||||
}catch (e: Exception){
|
||||
finalPath = context.getString(R.string.unfound_file)
|
||||
e.printStackTrace()
|
||||
|
|
@ -288,7 +285,9 @@ class DownloadWorker(
|
|||
runBlocking {
|
||||
dao.update(downloadItem)
|
||||
}
|
||||
return Result.failure()
|
||||
return Result.failure(
|
||||
Data.Builder().putString("output", "Download has been cancelled!").build()
|
||||
)
|
||||
}else{
|
||||
if (logDownloads && logFile.exists()){
|
||||
logFile.appendText("${it.message}\n")
|
||||
|
|
@ -310,7 +309,9 @@ class DownloadWorker(
|
|||
runBlocking {
|
||||
dao.update(downloadItem)
|
||||
}
|
||||
return Result.failure()
|
||||
return Result.failure(
|
||||
Data.Builder().putString("output", it.toString()).build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ import android.os.Handler
|
|||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import androidx.work.*
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
|
|
@ -48,13 +45,13 @@ class TerminalDownloadWorker(
|
|||
)
|
||||
|
||||
val downloadLocation = sharedPreferences.getString("command_path", context.getString(R.string.command_path))
|
||||
val tempFileDir = File(context.cacheDir.absolutePath + "/" + itemId)
|
||||
val tempFileDir = File(context.cacheDir.absolutePath + "/downloads/" + itemId)
|
||||
tempFileDir.delete()
|
||||
tempFileDir.mkdir()
|
||||
tempFileDir.mkdirs()
|
||||
|
||||
request.addOption(
|
||||
"--config-locations",
|
||||
File(context.cacheDir, "config${System.currentTimeMillis()}.txt").apply {
|
||||
File(context.cacheDir.absolutePath + "/downloads/config${System.currentTimeMillis()}.txt").apply {
|
||||
writeText(command)
|
||||
}.absolutePath
|
||||
)
|
||||
|
|
@ -93,7 +90,6 @@ class TerminalDownloadWorker(
|
|||
finalPath = moveFile(tempFileDir.absoluteFile, downloadLocation!!){ progress ->
|
||||
setProgressAsync(workDataOf("progress" to progress))
|
||||
}
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $finalPath", "id" to itemId, "log" to logDownloads))
|
||||
}catch (e: Exception){
|
||||
finalPath = context.getString(R.string.unfound_file)
|
||||
e.printStackTrace()
|
||||
|
|
@ -101,20 +97,7 @@ class TerminalDownloadWorker(
|
|||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||
}, 1000)
|
||||
}
|
||||
//put download in history
|
||||
// val incognito = sharedPreferences.getBoolean("incognito", false)
|
||||
// if (!incognito) {
|
||||
// val unixtime = System.currentTimeMillis() / 1000
|
||||
// val file = File(finalPath!!)
|
||||
// downloadItem.format.filesize = if (file.exists()) file.length() else 0L
|
||||
// val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPath, downloadItem.website, downloadItem.format)
|
||||
// runBlocking {
|
||||
// historyDao.insert(historyItem)
|
||||
// }
|
||||
// }
|
||||
// runBlocking {
|
||||
// dao.delete(downloadItem.id)
|
||||
// }
|
||||
|
||||
}.onFailure {
|
||||
if (it is YoutubeDL.CanceledException) {
|
||||
return Result.failure()
|
||||
|
|
@ -123,8 +106,6 @@ class TerminalDownloadWorker(
|
|||
if (logDownloads && logFile.exists()){
|
||||
logFile.appendText("${it.message}\n")
|
||||
}
|
||||
setProgressAsync(workDataOf("progress" to -1, "output" to it.message, "id" to itemId, "log" to logDownloads))
|
||||
|
||||
tempFileDir.delete()
|
||||
|
||||
Log.e(TAG, context.getString(R.string.failed_download), it)
|
||||
|
|
@ -133,7 +114,14 @@ class TerminalDownloadWorker(
|
|||
context.getString(R.string.failed_download), 0, 0, command.take(20),
|
||||
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
|
||||
)
|
||||
return Result.failure()
|
||||
|
||||
if (logDownloads && logFile.exists()){
|
||||
logFile.appendText("${it.message}\n")
|
||||
}
|
||||
|
||||
return Result.failure(
|
||||
Data.Builder().putString("output", it.message.toString()).build()
|
||||
)
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
|
|
|
|||
|
|
@ -105,13 +105,13 @@
|
|||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/linearlayout2_horizontalscrollview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:paddingTop="10dp"
|
||||
android:scrollbars="none"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/barrier"
|
||||
app:layout_constraintVertical_bias="1.0">
|
||||
app:layout_constraintTop_toBottomOf="@+id/barrier">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/linearLayout2"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@
|
|||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
|
|
@ -38,15 +37,23 @@
|
|||
android:paddingBottom="150dp"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
android:padding="10dp"
|
||||
android:fontFamily="monospace"
|
||||
android:textSize="15sp"
|
||||
android:gravity="bottom"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:textIsSelectable="true"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
android:padding="10dp"
|
||||
android:fontFamily="monospace"
|
||||
android:textSize="15sp"
|
||||
android:gravity="bottom"
|
||||
android:layout_width="match_parent"
|
||||
android:textIsSelectable="true"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue