This commit is contained in:
deniscerri 2023-05-01 12:51:22 +02:00
parent 6a2e827d77
commit 888dabc352
No known key found for this signature in database
GPG key ID: 95C43D517D830350
22 changed files with 115 additions and 76 deletions

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
<bytecodeTargetLevel target="17" />
</component>
</project>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="jbr-17" project-jdk-type="JavaSDK" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="corretto-17" project-jdk-type="JavaSDK" />
</project>

View file

@ -97,12 +97,12 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '11'
jvmTarget = '17'
}
splits {

View file

@ -61,4 +61,8 @@
public <init>(android.content.Context,androidx.work.WorkerParameters);
}
-dontobfuscate
-dontobfuscate
-dontwarn com.google.android.exoplayer2.**
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken

View file

@ -12,6 +12,9 @@ interface ResultDao {
@Query("SELECT COUNT(id) FROM results")
fun getCount() : LiveData<Int>
@Query("SELECT COUNT(id) FROM results")
fun getCountInt() :Int
@Query("SELECT * FROM results LIMIT 1")
fun getFirstResult() : ResultItem

View file

@ -33,7 +33,7 @@ import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.MergingMediaSource
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.ui.StyledPlayerView
import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@ -96,7 +96,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
}
@SuppressLint("RestrictedApi")
@SuppressLint("RestrictedApi", "SetTextI18n")
override fun setupDialog(dialog: Dialog, style: Int) {
super.setupDialog(dialog, style)
val view = LayoutInflater.from(context).inflate(R.layout.cut_video_sheet, null)
@ -106,14 +106,14 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
behavior = BottomSheetBehavior.from(view.parent as View)
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
behavior.peekHeight = displayMetrics.heightPixels / 2
behavior.peekHeight = displayMetrics.heightPixels
}
val renderersFactory = DefaultRenderersFactory(requireContext().applicationContext)
.setEnableDecoderFallback(true)
.setExtensionRendererMode(EXTENSION_RENDERER_MODE_PREFER)
.setMediaCodecSelector { mimeType, requiresSecureDecoder, requiresTunnelingDecoder ->
var decoderInfos: MutableList<MediaCodecInfo> =
var decoderInfo: MutableList<MediaCodecInfo> =
MediaCodecSelector.DEFAULT
.getDecoderInfos(
mimeType,
@ -122,13 +122,13 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
)
if (MimeTypes.VIDEO_H264 == mimeType) {
// copy the list because MediaCodecSelector.DEFAULT returns an unmodifiable list
decoderInfos = ArrayList(decoderInfos)
decoderInfos.reverse()
decoderInfo = ArrayList(decoderInfo)
decoderInfo.reverse()
}
decoderInfos
decoderInfo
}
val trackSelector = DefaultTrackSelector()
val trackSelector = DefaultTrackSelector(requireContext())
val loadControl = DefaultLoadControl()
player = ExoPlayer.Builder(requireContext(), renderersFactory)
@ -136,7 +136,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
.setLoadControl(loadControl)
.build()
val frame = view.findViewById<MaterialCardView>(R.id.frame_layout)
val videoView = view.findViewById<PlayerView>(R.id.video_view)
val videoView = view.findViewById<StyledPlayerView>(R.id.video_view)
videoView.player = player
timeSeconds = convertStringToTimestamp(item.duration)
chapters = emptyList()
@ -207,9 +207,9 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
progress.visibility = View.GONE
populateSuggestedChapters()
player.prepare()
player.seekTo((((rangeSlider.valueFrom.toInt() * timeSeconds) / 100) * 1000).toLong())
player.play()
player.prepare().apply {
player.play()
}
}catch (e: Exception){
progress.visibility = View.GONE
frame.visibility = View.GONE
@ -253,11 +253,12 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
}
@SuppressLint("SetTextI18n")
private fun initCutSection(){
fromTextInput.editText!!.setText("0:00")
toTextInput.editText!!.setText(item.duration)
rangeSlider.addOnChangeListener { rangeSlider, value, fromUser ->
rangeSlider.addOnChangeListener { rangeSlider, _, _ ->
val values = rangeSlider.values
val startTimestamp = (values[0].toInt() * timeSeconds) / 100
val endTimestamp = (values[1].toInt() * timeSeconds) / 100
@ -304,9 +305,9 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
val startValueTimeStampSeconds = (startValue.toInt() * timeSeconds) / 100
fromTextInput.editText!!.setText(infoUtil.formatIntegerDuration(startValueTimeStampSeconds, Locale.US))
return true;
return true
}
return false;
return false
}
})
@ -336,9 +337,9 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
val endValueTimeStampSeconds = (endValue.toInt() * timeSeconds) / 100
toTextInput.editText!!.setText(infoUtil.formatIntegerDuration(endValueTimeStampSeconds, Locale.US))
return true;
return true
}
return false;
return false
}
})
@ -409,7 +410,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
if (item.downloadSections.isNotBlank()){
chipGroup.removeAllViews()
item.downloadSections.split(";").forEachIndexed { index, it ->
item.downloadSections.split(";").forEachIndexed { _, it ->
if (it.isBlank()) return
if (it.contains(":")) createChip(it.replace(";", ""))
else createChapterChip(ChapterItem(0, 0, it), null)
@ -436,7 +437,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
if (chip.isChecked) {
rangeSlider.setValues(startingValue.toFloat(), endingValue.toFloat())
player.prepare()
player.seekTo((((startingValue * timeSeconds) / 100) * 1000).toLong())
player.seekTo((startTimestamp * 1000).toLong())
player.play()
}else {
player.seekTo(0)

View file

@ -71,7 +71,7 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
behavior = BottomSheetBehavior.from(view.parent as View)
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
behavior.peekHeight = displayMetrics.heightPixels
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}

View file

@ -69,12 +69,6 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
uiUtil = UiUtil(fileUtil)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val behavior = BottomSheetBehavior.from(view.parent as View)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
@SuppressLint("RestrictedApi", "NotifyDataSetChanged")
override fun setupDialog(dialog: Dialog, style: Int) {
super.setupDialog(dialog, style)
@ -85,7 +79,7 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
behavior = BottomSheetBehavior.from(view.parent as View)
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
behavior.peekHeight = displayMetrics.heightPixels / 2
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
listAdapter =

View file

@ -178,8 +178,10 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
containers
)
)
downloadItem.format.container = containerPreference!!
containerAutoCompleteTextView!!.setText(downloadItem.format.container, false)
downloadItem.format.container = if (containerPreference == getString(R.string.defaultValue)) "" else containerPreference!!
containerAutoCompleteTextView!!.setText(
downloadItem.format.container.ifEmpty { getString(R.string.defaultValue) },
false)
(container!!.editText as AutoCompleteTextView?)!!.onItemClickListener =
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long ->

View file

@ -45,12 +45,6 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val behavior = BottomSheetBehavior.from(view.parent as View)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
@SuppressLint("RestrictedApi")
override fun setupDialog(dialog: Dialog, style: Int) {
super.setupDialog(dialog, style)
@ -61,7 +55,7 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
behavior = BottomSheetBehavior.from(view.parent as View)
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
behavior.peekHeight = displayMetrics.heightPixels / 2
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
listAdapter =

View file

@ -358,6 +358,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
//val websites = historyRecyclerViewAdapter!!.websites
for (i in websites.indices) {
val w = websites[i]
if (w == "null" || w.isEmpty()) continue
val tmp = layoutinflater!!.inflate(R.layout.filter_chip, websiteGroup, false) as Chip
tmp.text = w
tmp.id = i

View file

@ -256,6 +256,13 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
WorkManager.getInstance(requireContext()).cancelUniqueWork(id.toString())
notificationUtil.cancelDownloadNotification(id)
downloadViewModel.deleteDownload(item!!)
Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + item.title, Snackbar.LENGTH_LONG)
.setAction(getString(R.string.undo)) {
lifecycleScope.launch(Dispatchers.IO) {
downloadViewModel.queueDownloads(listOf(item))
}
}.show()
}
deleteDialog.show()
}
@ -273,12 +280,6 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
ItemTouchHelper.LEFT -> {
val deletedItem = items[position]
removeItem(deletedItem.id)
Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
.setAction(getString(R.string.undo)) {
lifecycleScope.launch(Dispatchers.IO) {
downloadViewModel.queueDownloads(listOf(deletedItem))
}
}.show()
}
}

View file

@ -7,16 +7,11 @@ import android.os.Looper
import android.util.Log
import android.widget.Toast
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.reflect.TypeToken
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.serialization.json.Json
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
@ -137,7 +132,7 @@ class InfoUtil(private val context: Context) {
"thumb",
element.getJSONArray("videoThumbnails").getJSONObject(0).getString("url")
)
val v = createVideofromInvidiousJSON(element)
val v = createVideoFromInvidiousJSON(element)
if (v == null || v.thumb.isEmpty()) {
continue
}
@ -225,7 +220,7 @@ class InfoUtil(private val context: Context) {
val vids = res.getJSONArray("videos")
for (i in 0 until vids.length()) {
val element = vids.getJSONObject(i)
val v = createVideofromInvidiousJSON(element)
val v = createVideoFromInvidiousJSON(element)
if (v == null || v.thumb.isEmpty()) continue
v.playlistTitle = res.getString("title")
items.add(v)
@ -243,7 +238,7 @@ class InfoUtil(private val context: Context) {
if (key!!.isEmpty()) {
return if (useInvidous) {
val res = genericRequest(invidousURL + "videos/" + id)
if (res.length() == 0) getFromYTDL("https://www.youtube.com/watch?v=$id")[0] else createVideofromInvidiousJSON(
if (res.length() == 0) getFromYTDL("https://www.youtube.com/watch?v=$id")[0] else createVideoFromInvidiousJSON(
res
)
} else {
@ -293,7 +288,7 @@ class InfoUtil(private val context: Context) {
return video
}
private fun createVideofromInvidiousJSON(obj: JSONObject): ResultItem? {
private fun createVideoFromInvidiousJSON(obj: JSONObject): ResultItem? {
var video: ResultItem? = null
try {
val id = obj.getString("videoId")
@ -476,7 +471,7 @@ class InfoUtil(private val context: Context) {
for (i in 0 until res.length()) {
val element = res.getJSONObject(i)
if (element.getString("type") != "video") continue
val v = createVideofromInvidiousJSON(element)
val v = createVideoFromInvidiousJSON(element)
if (v == null || v.thumb.isEmpty()) continue
v.playlistTitle = context.getString(R.string.trendingPlaylist)
items.add(v)
@ -513,7 +508,7 @@ class InfoUtil(private val context: Context) {
val id = getIDFromYoutubeURL(url)
val res = genericRequest(invidousURL + "videos/" + id)
if (res.length() == 0) getFromYTDL(url)[0]!!
else createVideofromInvidiousJSON(
else createVideoFromInvidiousJSON(
res
)!!
}else{
@ -691,8 +686,29 @@ class InfoUtil(private val context: Context) {
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val jsonObject = JSONObject(youtubeDLResponse.out)
val thumbs = jsonObject.getJSONArray("thumbnails")
val thumb = thumbs.getJSONObject(thumbs.length() - 1).getString("url")
var author: String? = ""
if (jsonObject.has("uploader")) {
author = jsonObject.getString("uploader")
}
var duration = ""
runCatching {
if (jsonObject.has("duration")) {
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.getDefault())
}
}
var thumb: String? = ""
if (jsonObject.has("thumbnail")) {
thumb = jsonObject.getString("thumbnail")
} else if (jsonObject.has("thumbnails")) {
val thumbs = jsonObject.getJSONArray("thumbnails")
if (thumbs.length() > 0){
thumb = thumbs.getJSONObject(thumbs.length() - 1).getString("url")
}
}
val isPlaylist = jsonObject.has("playlist_count")
return ResultItem(
0,
@ -702,9 +718,9 @@ class InfoUtil(private val context: Context) {
}else{
jsonObject.getString("title")
},
jsonObject.getString("uploader"),
if (jsonObject.has("duration")) formatIntegerDuration(jsonObject.getInt("duration"), Locale.US) else "",
thumb,
author!!,
duration,
thumb!!,
jsonObject.getString("extractor"),
if (isPlaylist) jsonObject.getString("title") else "",
arrayListOf(),

View file

@ -8,6 +8,7 @@ import android.content.Intent
import android.net.Uri
import android.text.Editable
import android.text.TextWatcher
import android.util.DisplayMetrics
import android.util.Log
import android.view.View
import android.view.ViewGroup
@ -28,6 +29,7 @@ import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
@ -154,6 +156,7 @@ class UiUtil(private val fileUtil: FileUtil) {
}
bottomSheet.show()
bottomSheet.behavior.state = BottomSheetBehavior.STATE_EXPANDED
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
@ -195,6 +198,7 @@ class UiUtil(private val fileUtil: FileUtil) {
}
bottomSheet.show()
bottomSheet.behavior.state = BottomSheetBehavior.STATE_EXPANDED
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT

View file

@ -23,6 +23,8 @@ import com.deniscerri.ytdlnis.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File
@ -45,6 +47,7 @@ class DownloadWorker(
val dao = dbManager.downloadDao
val repository = DownloadRepository(dao)
val historyDao = dbManager.historyDao
val resultDao = dbManager.resultDao
val handler = Handler(Looper.getMainLooper())
@ -152,7 +155,7 @@ class DownloadWorker(
else if (audioQualityId == context.getString(R.string.worst_quality)) audioQualityId = "worstaudio"
val ext = downloadItem.format.container
if (audioQualityId.isBlank() || ext == "Default" || ext == context.getString(R.string.defaultValue)) request.addOption("-x")
if (audioQualityId.isBlank()) request.addOption("-x")
else request.addOption("-f", audioQualityId)
if(ext != "webm"){
@ -224,7 +227,7 @@ class DownloadWorker(
Log.e(TAG, formatArgument)
request.addOption("-f", formatArgument)
val format = downloadItem.format.container
if(format.isNotEmpty() && format != "Default" && format != context.getString(R.string.defaultValue)){
if(format.isNotEmpty()){
request.addOption("--merge-output-format", format)
if (format != "webm") {
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
@ -267,7 +270,7 @@ class DownloadWorker(
}
}
var wasQuickDownloaded = false
//update item if its incomplete
if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){
runCatching {
@ -275,10 +278,11 @@ class DownloadWorker(
val info = infoUtil.getMissingInfo(downloadItem.url)
if (downloadItem.title.isEmpty()) downloadItem.title = info?.title.toString()
if (downloadItem.author.isEmpty()) downloadItem.author = info?.author.toString()
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info?.thumb.toString()
downloadItem.duration = info?.duration.toString()
downloadItem.website = info?.website.toString()
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info?.thumb.toString()
runBlocking {
wasQuickDownloaded = resultDao.getCountInt() == 0
dao.update(downloadItem)
}
}
@ -345,6 +349,19 @@ class DownloadWorker(
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
)
if (wasQuickDownloaded){
runCatching {
setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id, "log" to false))
runBlocking {
infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
if (res != null) {
resultDao.insert(res)
}
}
}
}
}
runBlocking {
dao.delete(downloadItem.id)
}

View file

@ -64,7 +64,7 @@ class TerminalDownloadWorker(
request.addOption("--cookies", cookiesFile.absolutePath)
}
if (!command.contains("-P ")) request.addOption("-P", tempFileDir.absolutePath)
request.addOption("-P", tempFileDir.absolutePath)
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
val logFolder = File(context.filesDir.absolutePath + "/logs")

View file

@ -15,7 +15,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<com.google.android.exoplayer2.ui.PlayerView
<com.google.android.exoplayer2.ui.StyledPlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"

View file

@ -15,5 +15,5 @@
android:id="@+id/folder"
android:title="@string/command_directory"
android:icon="@drawable/baseline_folder_24"
app:showAsAction="collapseActionView|ifRoom"/>
app:showAsAction="ifRoom"/>
</menu>

View file

@ -11,7 +11,7 @@
</string-array>
<string-array name="audio_containers_values">
<item>Default</item>
<item />
<item>mp3</item>
<item>m4a</item>
<item>aac</item>
@ -29,7 +29,7 @@
</string-array>
<string-array name="video_containers_values">
<item>Default</item>
<item />
<item>mp4</item>
<item>mkv</item>
<item>webm</item>
@ -96,6 +96,7 @@
<item>tr</item>
<item>uk</item>
<item>vi</item>
<item>zh</item>
</string-array>

View file

@ -1,11 +1,8 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="BaseTheme" parent="Theme.Material3.Light.NoActionBar">
<item name="android:windowLightStatusBar">?attr/isLightTheme</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="windowActionModeOverlay">true</item>
<item name="alertDialogTheme">@style/ThemeOverlay.Material3.MaterialAlertDialog</item>
<item name="dialogCornerRadius">28dp</item>
<item name="switchPreferenceCompatStyle">@style/Preference.SwitchPreferenceCompat.Material3</item>

View file

@ -18,4 +18,5 @@
<locale android:name="tr-TR"/>
<locale android:name="uk"/>
<locale android:name="vi"/>
<locale android:name="zh-CN"/>
</locale-config>

View file

@ -6,7 +6,10 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
android.defaults.buildfeatures.buildconfig=true
android.enableJetifier=true
android.nonFinalResIds=false
android.nonTransitiveRClass=false
android.useAndroidX=true
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.