created download worker
This commit is contained in:
parent
ec465f8bda
commit
335be8299c
18 changed files with 367 additions and 373 deletions
|
|
@ -1,254 +0,0 @@
|
||||||
{
|
|
||||||
"formatVersion": 1,
|
|
||||||
"database": {
|
|
||||||
"version": 1,
|
|
||||||
"identityHash": "e2280d10a40623d3a51ecf5205500351",
|
|
||||||
"entities": [
|
|
||||||
{
|
|
||||||
"tableName": "results",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `website` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL)",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "id",
|
|
||||||
"columnName": "id",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "url",
|
|
||||||
"columnName": "url",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "title",
|
|
||||||
"columnName": "title",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "author",
|
|
||||||
"columnName": "author",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "duration",
|
|
||||||
"columnName": "duration",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "thumb",
|
|
||||||
"columnName": "thumb",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "website",
|
|
||||||
"columnName": "website",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "playlistTitle",
|
|
||||||
"columnName": "playlistTitle",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"columnNames": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"autoGenerate": true
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "history",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `type` TEXT NOT NULL, `time` INTEGER NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL)",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "id",
|
|
||||||
"columnName": "id",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "url",
|
|
||||||
"columnName": "url",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "title",
|
|
||||||
"columnName": "title",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "author",
|
|
||||||
"columnName": "author",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "duration",
|
|
||||||
"columnName": "duration",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "thumb",
|
|
||||||
"columnName": "thumb",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "type",
|
|
||||||
"columnName": "type",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "time",
|
|
||||||
"columnName": "time",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "downloadPath",
|
|
||||||
"columnName": "downloadPath",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "website",
|
|
||||||
"columnName": "website",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"columnNames": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"autoGenerate": true
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "downloads",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `startTime` TEXT NOT NULL, `endTime` TEXT NOT NULL, `thumb` TEXT NOT NULL, `formatId` INTEGER NOT NULL, `formatDesc` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `progress` REAL NOT NULL, `custom` INTEGER NOT NULL, `customTemplateId` INTEGER NOT NULL)",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "id",
|
|
||||||
"columnName": "id",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "url",
|
|
||||||
"columnName": "url",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "title",
|
|
||||||
"columnName": "title",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "author",
|
|
||||||
"columnName": "author",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "startTime",
|
|
||||||
"columnName": "startTime",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "endTime",
|
|
||||||
"columnName": "endTime",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "thumb",
|
|
||||||
"columnName": "thumb",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "formatId",
|
|
||||||
"columnName": "formatId",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "formatDesc",
|
|
||||||
"columnName": "formatDesc",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "downloadPath",
|
|
||||||
"columnName": "downloadPath",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "website",
|
|
||||||
"columnName": "website",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "downloadSize",
|
|
||||||
"columnName": "downloadSize",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "progress",
|
|
||||||
"columnName": "progress",
|
|
||||||
"affinity": "REAL",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "custom",
|
|
||||||
"columnName": "custom",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "customTemplateId",
|
|
||||||
"columnName": "customTemplateId",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"columnNames": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"autoGenerate": true
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"views": [],
|
|
||||||
"setupQueries": [
|
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e2280d10a40623d3a51ecf5205500351')"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -67,7 +67,7 @@
|
||||||
android:name=".DownloaderService"
|
android:name=".DownloaderService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
<receiver android:name=".receiver.NotificationReceiver" />
|
<receiver android:name=".receiver.CancelDownloadNotificationReceiver" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
|
|
||||||
|
|
@ -432,6 +432,8 @@ class DownloaderService : Service() {
|
||||||
Log.e(TAG, downloadInfo.downloadPath)
|
Log.e(TAG, downloadInfo.downloadPath)
|
||||||
downloadInfo.downloadType = type
|
downloadInfo.downloadType = type
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (activity in activities.keys) {
|
for (activity in activities.keys) {
|
||||||
activity.runOnUiThread {
|
activity.runOnUiThread {
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,17 @@ import android.content.Context
|
||||||
import androidx.room.Database
|
import androidx.room.Database
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||||
import com.deniscerri.ytdlnis.database.dao.HistoryDao
|
import com.deniscerri.ytdlnis.database.dao.HistoryDao
|
||||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||||
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [ResultItem::class, HistoryItem::class, DownloadItem::class],
|
entities = [ResultItem::class, HistoryItem::class, DownloadItem::class, CommandTemplate::class],
|
||||||
version = 1,
|
version = 1,
|
||||||
autoMigrations = []
|
autoMigrations = []
|
||||||
)
|
)
|
||||||
|
|
@ -20,6 +22,7 @@ abstract class DBManager : RoomDatabase(){
|
||||||
abstract val resultDao : ResultDao
|
abstract val resultDao : ResultDao
|
||||||
abstract val historyDao : HistoryDao
|
abstract val historyDao : HistoryDao
|
||||||
abstract val downloadDao : DownloadDao
|
abstract val downloadDao : DownloadDao
|
||||||
|
abstract val commandTemplateDao : CommandTemplateDao
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
//prevents multiple instances of db getting created at the same time
|
//prevents multiple instances of db getting created at the same time
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.deniscerri.ytdlnis.database.dao
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.room.*
|
||||||
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface CommandTemplateDao {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM commandTemplates")
|
||||||
|
fun getAllTemplates() : List<CommandTemplate>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM commandTemplates WHERE id=:id LIMIT 1")
|
||||||
|
fun getTemplateById(id: Int) : CommandTemplate
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
suspend fun insert(item: CommandTemplate)
|
||||||
|
|
||||||
|
@Query("DELETE FROM commandTemplates")
|
||||||
|
suspend fun deleteAll()
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(item: CommandTemplate)
|
||||||
|
|
||||||
|
@Update(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun update(item: CommandTemplate)
|
||||||
|
}
|
||||||
|
|
@ -10,10 +10,13 @@ import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
interface DownloadDao {
|
interface DownloadDao {
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads")
|
@Query("SELECT * FROM downloads")
|
||||||
fun getAllDownloads() : LiveData<List<DownloadItem>>
|
fun getAllDownloads() : List<DownloadItem>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM downloads WHERE status='Queued'")
|
||||||
|
fun getQueuedDownloads() : List<DownloadItem>
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
|
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
|
||||||
suspend fun getDownloadById(id: Int) : DownloadItem
|
fun getDownloadById(id: Int) : DownloadItem
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
suspend fun insert(item: DownloadItem)
|
suspend fun insert(item: DownloadItem)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.deniscerri.ytdlnis.database.models
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "downloads")
|
||||||
|
data class CommandTemplate(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
val id: Int,
|
||||||
|
val title: String,
|
||||||
|
val content: String
|
||||||
|
)
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.deniscerri.ytdlnis.database.models
|
package com.deniscerri.ytdlnis.database.models
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
|
@ -10,15 +11,21 @@ data class DownloadItem(
|
||||||
val url: String,
|
val url: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val author: String,
|
val author: String,
|
||||||
val startTime: String,
|
|
||||||
val endTime: String,
|
|
||||||
val thumb: String,
|
val thumb: String,
|
||||||
val formatId: Int,
|
val duration: String,
|
||||||
|
val type: String,
|
||||||
|
val audioQualityId: String,
|
||||||
|
val videoQualityId: String,
|
||||||
|
val customTemplateId: Int,
|
||||||
|
val audioFormat: String,
|
||||||
|
val videoFormat: String,
|
||||||
val formatDesc: String,
|
val formatDesc: String,
|
||||||
|
@ColumnInfo(defaultValue = "0")
|
||||||
|
val removeAudio: Boolean,
|
||||||
val downloadPath: String,
|
val downloadPath: String,
|
||||||
val website: String,
|
val website: String,
|
||||||
val downloadSize: String,
|
val downloadSize: String,
|
||||||
val progress: Float,
|
val playlistTitle: String,
|
||||||
val custom: Boolean,
|
@ColumnInfo(defaultValue = "Queued")
|
||||||
val customTemplateId: Int
|
var status: String
|
||||||
)
|
)
|
||||||
|
|
@ -5,8 +5,6 @@ import androidx.room.PrimaryKey
|
||||||
|
|
||||||
@Entity(tableName = "history")
|
@Entity(tableName = "history")
|
||||||
data class HistoryItem(
|
data class HistoryItem(
|
||||||
@PrimaryKey(autoGenerate = true)
|
|
||||||
var id: Int,
|
|
||||||
val url: String,
|
val url: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val author: String,
|
val author: String,
|
||||||
|
|
@ -16,5 +14,7 @@ data class HistoryItem(
|
||||||
val time: Long,
|
val time: Long,
|
||||||
val downloadPath: String,
|
val downloadPath: String,
|
||||||
val website: String
|
val website: String
|
||||||
//val isQueuedDownload: Int
|
){
|
||||||
)
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
var id: Int? = null
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,6 @@ import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
|
||||||
class DownloadRepository(private val downloadDao: DownloadDao) {
|
class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
val allDownloads : LiveData<List<DownloadItem>> = downloadDao.getAllDownloads()
|
|
||||||
|
|
||||||
suspend fun insert(item: DownloadItem){
|
suspend fun insert(item: DownloadItem){
|
||||||
downloadDao.insert(item)
|
downloadDao.insert(item)
|
||||||
|
|
@ -19,5 +18,14 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
downloadDao.update(item)
|
downloadDao.update(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getQueuedDownloads() : List<DownloadItem>{
|
||||||
|
var items = downloadDao.getQueuedDownloads();
|
||||||
|
for (i in items){
|
||||||
|
i.status = "Downloading"
|
||||||
|
downloadDao.update(i)
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.deniscerri.ytdlnis.receiver
|
||||||
|
|
||||||
|
import android.content.*
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
|
||||||
|
class CancelDownloadNotificationReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(c: Context, intent: Intent) {
|
||||||
|
val message = intent.getStringExtra("cancel")
|
||||||
|
val id = intent.getIntExtra("workID", 0)
|
||||||
|
if (message != null) {
|
||||||
|
WorkManager.getInstance(c).cancelAllWorkByTag(id.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
package com.deniscerri.ytdlnis.receiver
|
|
||||||
|
|
||||||
import android.content.*
|
|
||||||
import android.os.IBinder
|
|
||||||
import com.deniscerri.ytdlnis.DownloaderService
|
|
||||||
import com.deniscerri.ytdlnis.DownloaderService.LocalBinder
|
|
||||||
import com.deniscerri.ytdlnis.service.IDownloaderService
|
|
||||||
|
|
||||||
class NotificationReceiver : BroadcastReceiver() {
|
|
||||||
var downloaderService: DownloaderService? = null
|
|
||||||
private var iDownloaderService: IDownloaderService? = null
|
|
||||||
private var context: Context? = null
|
|
||||||
private val serviceConnection: ServiceConnection = object : ServiceConnection {
|
|
||||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
|
||||||
downloaderService = (service as LocalBinder).service
|
|
||||||
iDownloaderService = service
|
|
||||||
cancelDownload()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onServiceDisconnected(componentName: ComponentName) {
|
|
||||||
downloaderService = null
|
|
||||||
iDownloaderService = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onReceive(c: Context, intent: Intent) {
|
|
||||||
context = c
|
|
||||||
val message = intent.getStringExtra("cancel")
|
|
||||||
if (message != null) {
|
|
||||||
val serviceIntent = Intent(context!!.applicationContext, DownloaderService::class.java)
|
|
||||||
serviceIntent.putExtra("rebind", true)
|
|
||||||
context!!.applicationContext.bindService(
|
|
||||||
serviceIntent,
|
|
||||||
serviceConnection,
|
|
||||||
Context.BIND_AUTO_CREATE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cancelDownload() {
|
|
||||||
try {
|
|
||||||
iDownloaderService!!.cancelDownload(true)
|
|
||||||
context!!.applicationContext.unbindService(serviceConnection)
|
|
||||||
context!!.applicationContext.stopService(
|
|
||||||
Intent(
|
|
||||||
context!!.applicationContext,
|
|
||||||
DownloaderService::class.java
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} catch (ignored: Exception) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,7 +10,7 @@ import android.graphics.BitmapFactory
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.receiver.NotificationReceiver
|
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
|
||||||
|
|
||||||
class NotificationUtil(var context: Context) {
|
class NotificationUtil(var context: Context) {
|
||||||
private val downloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_SERVICE_CHANNEL_ID)
|
private val downloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||||
|
|
@ -53,7 +53,7 @@ class NotificationUtil(var context: Context) {
|
||||||
pendingIntent: PendingIntent?,
|
pendingIntent: PendingIntent?,
|
||||||
title: String?,
|
title: String?,
|
||||||
) : Notification {
|
) : Notification {
|
||||||
val intent = Intent(context, NotificationReceiver::class.java)
|
val intent = Intent(context, CancelDownloadNotificationReceiver::class.java)
|
||||||
|
|
||||||
return fileTransferNotificationBuilder
|
return fileTransferNotificationBuilder
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
|
|
@ -92,12 +92,14 @@ class NotificationUtil(var context: Context) {
|
||||||
fun createDownloadServiceNotification(
|
fun createDownloadServiceNotification(
|
||||||
pendingIntent: PendingIntent?,
|
pendingIntent: PendingIntent?,
|
||||||
title: String?,
|
title: String?,
|
||||||
|
workID: Int,
|
||||||
channel: String
|
channel: String
|
||||||
): Notification {
|
): Notification {
|
||||||
val notificationBuilder = getBuilder(channel)
|
val notificationBuilder = getBuilder(channel)
|
||||||
|
|
||||||
val intent = Intent(context, NotificationReceiver::class.java)
|
val intent = Intent(context, CancelDownloadNotificationReceiver::class.java)
|
||||||
intent.putExtra("cancel", "")
|
intent.putExtra("cancel", "")
|
||||||
|
intent.putExtra("workID", workID)
|
||||||
val cancelNotificationPendingIntent = PendingIntent.getBroadcast(
|
val cancelNotificationPendingIntent = PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
0,
|
0,
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
package com.deniscerri.ytdlnis.work;
|
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.work.ForegroundInfo;
|
|
||||||
import androidx.work.Worker;
|
|
||||||
import androidx.work.WorkerParameters;
|
|
||||||
|
|
||||||
import com.deniscerri.ytdlnis.MainActivity;
|
|
||||||
import com.deniscerri.ytdlnis.database.Video;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import kotlin.random.Random;
|
|
||||||
|
|
||||||
public class DownloadWorker extends Worker {
|
|
||||||
private Context context;
|
|
||||||
private WorkerParameters workerParams;
|
|
||||||
public DownloadWorker(
|
|
||||||
@NonNull Context appContext,
|
|
||||||
@NonNull WorkerParameters workerParams) {
|
|
||||||
super(appContext, workerParams);
|
|
||||||
this.context = appContext;
|
|
||||||
this.workerParams = workerParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Result doWork() {
|
|
||||||
//TODO
|
|
||||||
return Result.success();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
253
app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
Normal file
253
app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
Normal file
|
|
@ -0,0 +1,253 @@
|
||||||
|
package com.deniscerri.ytdlnis.work
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.provider.DocumentsContract
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.ForegroundInfo
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import androidx.work.workDataOf
|
||||||
|
import com.deniscerri.ytdlnis.BuildConfig
|
||||||
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
|
import com.deniscerri.ytdlnis.R
|
||||||
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||||
|
import java.io.File
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadWorker(
|
||||||
|
private val context: Context,
|
||||||
|
workerParams: WorkerParameters
|
||||||
|
) : CoroutineWorker(context, workerParams) {
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
workID = inputData.getInt("workID", SystemClock.uptimeMillis().toInt())
|
||||||
|
val notificationUtil = NotificationUtil(context)
|
||||||
|
val dbManager = DBManager.getInstance(context)
|
||||||
|
val dao = dbManager.downloadDao
|
||||||
|
val repository = DownloadRepository(dao)
|
||||||
|
val commandTemplateDao = dbManager.commandTemplateDao
|
||||||
|
val historyDao = dbManager.historyDao
|
||||||
|
|
||||||
|
queue = repository.getQueuedDownloads() as MutableList<DownloadItem>
|
||||||
|
|
||||||
|
val intent = Intent(context, MainActivity::class.java)
|
||||||
|
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, queue[0].title, workID, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||||
|
val foregroundInfo = ForegroundInfo(workID, notification)
|
||||||
|
setForeground(foregroundInfo)
|
||||||
|
|
||||||
|
while (queue.isNotEmpty()){
|
||||||
|
val item = dao.getDownloadById(queue[0].id)
|
||||||
|
queue.removeFirst()
|
||||||
|
val url = item.url
|
||||||
|
val request = YoutubeDLRequest(url)
|
||||||
|
val type = item.type
|
||||||
|
val downloadLocation = item.downloadPath
|
||||||
|
|
||||||
|
val tempFolder = StringBuilder(context.cacheDir.absolutePath + """/${item.title}##${item.type}""")
|
||||||
|
when(type){
|
||||||
|
"audio" -> tempFolder.append("##${item.audioQualityId}")
|
||||||
|
"video" -> tempFolder.append("##${item.videoQualityId}")
|
||||||
|
"command" -> tempFolder.append("##${item.customTemplateId}")
|
||||||
|
}
|
||||||
|
var tempFileDir = File(tempFolder.toString())
|
||||||
|
tempFileDir.delete()
|
||||||
|
tempFileDir.mkdir()
|
||||||
|
|
||||||
|
val sharedPreferences = context.getSharedPreferences("root_preferences",
|
||||||
|
Service.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
val aria2 = sharedPreferences.getBoolean("aria2", false)
|
||||||
|
if (aria2) {
|
||||||
|
request.addOption("--downloader", "libaria2c.so")
|
||||||
|
request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"")
|
||||||
|
} else {
|
||||||
|
val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
|
||||||
|
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
|
||||||
|
}
|
||||||
|
val limitRate = sharedPreferences.getString("limit_rate", "")
|
||||||
|
if (limitRate != "") request.addOption("-r", limitRate!!)
|
||||||
|
val writeThumbnail = sharedPreferences.getBoolean("write_thumbnail", false)
|
||||||
|
if (writeThumbnail) {
|
||||||
|
request.addOption("--write-thumbnail")
|
||||||
|
request.addOption("--convert-thumbnails", "png")
|
||||||
|
}
|
||||||
|
request.addOption("--no-mtime")
|
||||||
|
val sponsorBlockFilters = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
|
||||||
|
if (sponsorBlockFilters!!.isNotEmpty()) {
|
||||||
|
val filters = java.lang.String.join(",", sponsorBlockFilters)
|
||||||
|
request.addOption("--sponsorblock-remove", filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
when(type){
|
||||||
|
"audio" -> {
|
||||||
|
request.addOption("-x")
|
||||||
|
var audioQualityId : String = item.audioQualityId
|
||||||
|
if (audioQualityId == "0") audioQualityId = "ba"
|
||||||
|
var format = item.audioFormat
|
||||||
|
if (format.isEmpty()){
|
||||||
|
format = sharedPreferences.getString("audio_format", "")!!
|
||||||
|
}
|
||||||
|
request.addOption("-f", audioQualityId)
|
||||||
|
request.addOption("--audio-format", format)
|
||||||
|
request.addOption("--embed-metadata")
|
||||||
|
|
||||||
|
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
||||||
|
if (embedThumb) {
|
||||||
|
request.addOption("--embed-thumbnail")
|
||||||
|
request.addOption("--convert-thumbnails", "png")
|
||||||
|
try {
|
||||||
|
val config = File(context.cacheDir, "config" + item.title + "##" + item.audioQualityId + ".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)
|
||||||
|
request.addOption("--config", config.absolutePath)
|
||||||
|
} catch (ignored: Exception) {}
|
||||||
|
}
|
||||||
|
request.addOption("--parse-metadata", "%(release_year,upload_date)s:%(meta_date)s")
|
||||||
|
request.addCommands(listOf("--replace-in-metadata", "title", ".*.", item.title))
|
||||||
|
request.addCommands(listOf("--replace-in-metadata", "uploader", ".*.", item.author))
|
||||||
|
|
||||||
|
if (item.playlistTitle.isNotEmpty()) {
|
||||||
|
request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s")
|
||||||
|
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(meta_track)s")
|
||||||
|
} else {
|
||||||
|
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
|
||||||
|
}
|
||||||
|
request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
|
||||||
|
}
|
||||||
|
"video" -> {
|
||||||
|
if (downloadLocation == context.getString(R.string.video_path)){
|
||||||
|
tempFileDir = File(context.getString(R.string.video_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
val addChapters = sharedPreferences.getBoolean("add_chapters", false)
|
||||||
|
if (addChapters) {
|
||||||
|
request.addOption("--sponsorblock-mark", "all")
|
||||||
|
}
|
||||||
|
val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
|
||||||
|
if (embedSubs) {
|
||||||
|
request.addOption("--embed-subs", "")
|
||||||
|
}
|
||||||
|
var videoQualityId = item.videoQualityId
|
||||||
|
val audioQualityId = item.audioQualityId
|
||||||
|
if (videoQualityId.isEmpty()) videoQualityId = "bestvideo"
|
||||||
|
val formatArgument = StringBuilder(videoQualityId)
|
||||||
|
if (videoQualityId != "worst"){
|
||||||
|
if (audioQualityId != "0") formatArgument.append("+", audioQualityId, "/best")
|
||||||
|
}
|
||||||
|
request.addOption("-f", formatArgument.toString())
|
||||||
|
var format = item.videoFormat
|
||||||
|
if (format.isNotEmpty()) {
|
||||||
|
format = sharedPreferences.getString("video_format", "")!!
|
||||||
|
if (format != "DEFAULT") request.addOption("--merge-output-format", format)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format != "webm") {
|
||||||
|
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
||||||
|
if (embedThumb) {
|
||||||
|
request.addOption("--embed-thumbnail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
|
||||||
|
}
|
||||||
|
"command" -> {
|
||||||
|
val commandRegex = "\"([^\"]*)\"|(\\S+)"
|
||||||
|
val command = commandTemplateDao.getTemplateById(item.customTemplateId)
|
||||||
|
val m = Pattern.compile(commandRegex).matcher(command.content)
|
||||||
|
while (m.find()) {
|
||||||
|
if (m.group(1) != null) {
|
||||||
|
request.addOption(m.group(1)!!)
|
||||||
|
} else {
|
||||||
|
request.addOption(m.group(2)!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runCatching {
|
||||||
|
YoutubeDL.getInstance().execute(request, item.id.toString()){ progress, _, line ->
|
||||||
|
setProgressAsync(workDataOf("progress" to progress.toInt()))
|
||||||
|
var title: String? = context.getString(R.string.download_start)
|
||||||
|
if (queue.isNotEmpty()) {
|
||||||
|
title = queue[0].title
|
||||||
|
}
|
||||||
|
notificationUtil.updateDownloadNotification(
|
||||||
|
workID,
|
||||||
|
line, progress.toInt(), queue.size, title,
|
||||||
|
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.onSuccess {
|
||||||
|
//move file from internal to set download directory
|
||||||
|
moveFile(tempFileDir.absoluteFile, downloadLocation){ progress ->
|
||||||
|
setProgressAsync(workDataOf("progress" to progress))
|
||||||
|
}
|
||||||
|
//put download in history
|
||||||
|
val incognito = sharedPreferences.getBoolean("incognito", false)
|
||||||
|
if (!incognito) {
|
||||||
|
val unixtime = System.currentTimeMillis() / 1000
|
||||||
|
val historyItem = HistoryItem(item.url, item.title, item.author, item.duration, item.thumb, item.type, unixtime, item.downloadPath, item.website)
|
||||||
|
historyDao.insert(historyItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
}.onFailure {
|
||||||
|
tempFileDir.delete()
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
|
||||||
|
Log.e(DownloadWorker.TAG, context.getString(R.string.failed_download), it)
|
||||||
|
}
|
||||||
|
notificationUtil.updateDownloadNotification(
|
||||||
|
workID,
|
||||||
|
context.getString(R.string.failed_download), 0, 0, item.title,
|
||||||
|
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
|
||||||
|
)
|
||||||
|
return Result.failure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDownloadLocation(type: String, context: Context): String? {
|
||||||
|
val sharedPreferences = context.getSharedPreferences("root_preferences",
|
||||||
|
Service.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
val downloadsDir: String? = if (type == "audio") {
|
||||||
|
sharedPreferences.getString("music_path", context.getString(R.string.music_path))
|
||||||
|
} else {
|
||||||
|
sharedPreferences.getString("video_path", context.getString(R.string.video_path))
|
||||||
|
}
|
||||||
|
return downloadsDir
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun moveFile(originDir: File, downLocation: String, progress: (progress: Int) -> Unit){
|
||||||
|
val destDir = Uri.parse(downLocation).run {
|
||||||
|
DocumentsContract.buildChildDocumentsUriUsingTree(this, DocumentsContract.getTreeDocumentId(this))
|
||||||
|
}
|
||||||
|
val fileUtil = FileUtil()
|
||||||
|
fileUtil.moveFile(originDir, context, destDir){ p ->
|
||||||
|
progress(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
var workID: Int = 0
|
||||||
|
const val TAG = "DownloadWorker"
|
||||||
|
lateinit var queue: MutableList<DownloadItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ import android.provider.DocumentsContract
|
||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.ForegroundInfo
|
import androidx.work.ForegroundInfo
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
|
import androidx.work.workDataOf
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
|
|
@ -39,6 +40,7 @@ class FileTransferWorker(
|
||||||
setForeground(foregroundInfo)
|
setForeground(foregroundInfo)
|
||||||
|
|
||||||
fileUtil.moveFile(originDir, context, destDir){ progress ->
|
fileUtil.moveFile(originDir, context, destDir){ progress ->
|
||||||
|
setProgressAsync(workDataOf("progress" to progress))
|
||||||
notificationUtil.updateFileTransferNotification(id, progress)
|
notificationUtil.updateFileTransferNotification(id, progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,11 +49,11 @@
|
||||||
android:id="@+id/downloads_title"
|
android:id="@+id/downloads_title"
|
||||||
android:layout_width="300dp"
|
android:layout_width="300dp"
|
||||||
android:layout_height="100dp"
|
android:layout_height="100dp"
|
||||||
android:paddingTop="15dp"
|
android:paddingTop="10dp"
|
||||||
android:paddingRight="20dp"
|
android:paddingEnd="10dp"
|
||||||
android:paddingBottom="20dp"
|
android:paddingBottom="10dp"
|
||||||
android:paddingLeft="20dp"
|
android:paddingStart="10dp"
|
||||||
android:textSize="16sp"
|
android:textSize="15sp"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:shadowRadius="2"
|
android:shadowRadius="2"
|
||||||
|
|
@ -67,9 +67,9 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="bottom"
|
android:gravity="bottom"
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
android:paddingRight="20dp"
|
android:paddingRight="10dp"
|
||||||
android:paddingBottom="10dp"
|
android:paddingBottom="10dp"
|
||||||
android:paddingLeft="20dp"
|
android:paddingLeft="10dp"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
|
@ -82,11 +82,11 @@
|
||||||
android:id="@+id/downloads_info_time"
|
android:id="@+id/downloads_info_time"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="bottom|right"
|
android:gravity="bottom|end"
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
android:paddingRight="20dp"
|
android:paddingRight="10dp"
|
||||||
android:paddingBottom="10dp"
|
android:paddingBottom="10dp"
|
||||||
android:paddingLeft="20dp"
|
android:paddingLeft="10dp"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
android:paddingEnd="10dp"
|
android:paddingEnd="10dp"
|
||||||
android:paddingStart="10dp"
|
android:paddingStart="10dp"
|
||||||
android:textSize="15sp"
|
android:textSize="17sp"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:shadowRadius="2"
|
android:shadowRadius="2"
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
android:paddingEnd="10dp"
|
android:paddingEnd="10dp"
|
||||||
android:paddingBottom="5dp"
|
android:paddingBottom="5dp"
|
||||||
android:paddingStart="10dp"
|
android:paddingStart="10dp"
|
||||||
android:textSize="10sp"
|
android:textSize="12sp"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:shadowRadius="1.5"
|
android:shadowRadius="1.5"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue