From 1f03f96d7a498eaa583a07d8f840590c979533e1 Mon Sep 17 00:00:00 2001
From: Matthieu <24-artectrex@users.noreply.shinice.net>
Date: Fri, 10 Jun 2022 23:41:29 +0200
Subject: [PATCH 1/5] Rudimentary ffmpeg thumbnail
---
app/build.gradle | 6 +-
app/src/main/AndroidManifest.xml | 5 ++
.../app/postCreation/PostCreationActivity.kt | 23 ++++----
.../photoEdit/VideoEditActivity.kt | 57 +++++++++++++++++++
.../main/res/layout/activity_video_edit.xml | 15 +++++
app/src/main/res/values/strings.xml | 1 +
build.gradle | 2 +-
gradle/wrapper/gradle-wrapper.properties | 2 +-
8 files changed, 95 insertions(+), 16 deletions(-)
create mode 100644 app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt
create mode 100644 app/src/main/res/layout/activity_video_edit.xml
diff --git a/app/build.gradle b/app/build.gradle
index d54e1eaf..e09ea30c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -133,7 +133,7 @@ dependencies {
// Use the most recent version of CameraX
- def cameraX_version = '1.1.0-rc01'
+ def cameraX_version = '1.1.0-rc02'
implementation "androidx.camera:camera-core:$cameraX_version"
implementation "androidx.camera:camera-camera2:$cameraX_version"
// CameraX Lifecycle library
@@ -142,8 +142,6 @@ dependencies {
// CameraX View class
implementation "androidx.camera:camera-view:$cameraX_version"
- implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
-
def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
@@ -155,6 +153,8 @@ dependencies {
* ----------------------------------------------------------
*/
+ implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
+ implementation 'com.arthenica:ffmpeg-kit-full:4.5.1-1.LTS'
implementation 'com.google.android.material:material:1.6.1'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4a3bdf48..89f1821d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -26,6 +26,11 @@
android:name=".posts.AlbumActivity"
android:exported="false"
android:theme="@style/AppTheme.ActionBar.Transparent"/>
+
+
}
- }.show()
- } else {
- val intent = Intent(this, PhotoEditActivity::class.java)
- .putExtra(PhotoEditActivity.PICTURE_URI, photoData[position].imageUri)
- .putExtra(PhotoEditActivity.PICTURE_POSITION, position)
- editResultContract.launch(intent)
- }
+ val intent = Intent(
+ this,
+ if(photoData[position].video) VideoEditActivity::class.java else PhotoEditActivity::class.java
+ )
+ .putExtra(PhotoEditActivity.PICTURE_URI, photoData[position].imageUri)
+ .putExtra(PhotoEditActivity.PICTURE_POSITION, position)
+
+ editResultContract.launch(intent)
+
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt
new file mode 100644
index 00000000..53c3240d
--- /dev/null
+++ b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt
@@ -0,0 +1,57 @@
+package org.pixeldroid.app.postCreation.photoEdit
+
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import androidx.core.net.toUri
+import com.arthenica.ffmpegkit.*
+import com.arthenica.ffmpegkit.MediaInformation.KEY_DURATION
+import com.bumptech.glide.Glide
+import org.pixeldroid.app.databinding.ActivityVideoEditBinding
+import org.pixeldroid.app.utils.BaseActivity
+import java.io.File
+
+
+class VideoEditActivity : BaseActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val binding = ActivityVideoEditBinding.inflate(layoutInflater)
+
+ setContentView(binding.root)
+ val uri = intent.getParcelableExtra(PhotoEditActivity.PICTURE_URI) as Uri?
+ val videoPosition = intent.getIntExtra(PhotoEditActivity.PICTURE_POSITION, -1)
+
+ val inputVideoPath =if(uri.toString().startsWith("content://")) FFmpegKitConfig.getSafParameterForRead(this, uri) else uri.toString()
+ val inputVideoPath2 =if(uri.toString().startsWith("content://")) FFmpegKitConfig.getSafParameterForRead(this, uri) else uri.toString()
+ val mediaInformation: MediaInformation? = FFprobeKit.getMediaInformation(inputVideoPath).mediaInformation
+
+ val duration: Long? = mediaInformation?.getNumberProperty(KEY_DURATION)
+
+ val file = File.createTempFile("temp_img", ".png").toUri()
+
+ val outputImagePath =if(file.toString().startsWith("content://")) FFmpegKitConfig.getSafParameterForWrite(this, file) else file.toString()
+
+ val session = FFmpegKit.execute(
+ "-i $inputVideoPath2 -filter_complex \"select='not(mod(n,1000))',scale=240:-1,tile=layout=4x1\" -vframes 1 -q:v 2 -y $outputImagePath"
+ )
+ if (ReturnCode.isSuccess(session.returnCode)) {
+ Glide.with(this).load(file).into(binding.thumbnails)
+ // SUCCESS
+ } else if (ReturnCode.isCancel(session.returnCode)) {
+
+ // CANCEL
+ } else {
+
+ // FAILURE
+ Log.d("VideoEditActivity",
+ String.format("Command failed with state %s and rc %s.%s",
+ session.state,
+ session.returnCode,
+ session.failStackTrace))
+ }
+
+ }
+ companion object {
+ const val VIDEO_TAG = "VideoEditTag"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_video_edit.xml b/app/src/main/res/layout/activity_video_edit.xml
new file mode 100644
index 00000000..d7c9300f
--- /dev/null
+++ b/app/src/main/res/layout/activity_video_edit.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 885c39c2..0e756cb5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -262,4 +262,5 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"
Storage permission not granted, grant the permission in settings if you want to let PixelDroid show the thumbnail
Play video
Video editing is not yet supported
+ Reel showing thumbnails of the video you are editing
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 5d5d8d52..c2731152 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.1.3'
+ classpath 'com.android.tools.build:gradle:7.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 39b9af83..02b00d99 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Tue Jun 07 20:42:16 CEST 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
From 8cecfa3de6e23626555ade3208db73339775bec4 Mon Sep 17 00:00:00 2001
From: Matthieu <24-artectrex@users.noreply.shinice.net>
Date: Sat, 18 Jun 2022 22:21:19 +0200
Subject: [PATCH 2/5] Video edit
---
app/src/main/AndroidManifest.xml | 3 +-
.../app/postCreation/PostCreationActivity.kt | 199 ++++++++++--
.../app/postCreation/PostCreationViewModel.kt | 30 ++
.../app/postCreation/carousel/CarouselItem.kt | 3 +-
.../postCreation/carousel/ImageCarousel.kt | 66 ++--
.../photoEdit/PhotoEditActivity.kt | 6 +-
.../photoEdit/VideoEditActivity.kt | 282 ++++++++++++++++--
.../java/org/pixeldroid/app/utils/Utils.kt | 9 +-
app/src/main/res/drawable/double_circle.xml | 11 +
app/src/main/res/drawable/error.xml | 5 +
app/src/main/res/drawable/selector_mute.xml | 9 +
app/src/main/res/drawable/thumb_left.xml | 25 ++
app/src/main/res/drawable/thumb_right.xml | 29 ++
app/src/main/res/drawable/volume_off.xml | 5 +
app/src/main/res/drawable/volume_up.xml | 5 +
.../main/res/layout/activity_video_edit.xml | 105 ++++++-
app/src/main/res/layout/image_carousel.xml | 26 ++
.../{edit_photo_menu.xml => edit_menu.xml} | 4 +-
app/src/main/res/values/strings.xml | 7 +
gradle/wrapper/gradle-wrapper.properties | 2 +-
20 files changed, 744 insertions(+), 87 deletions(-)
create mode 100644 app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt
create mode 100644 app/src/main/res/drawable/double_circle.xml
create mode 100644 app/src/main/res/drawable/error.xml
create mode 100644 app/src/main/res/drawable/selector_mute.xml
create mode 100644 app/src/main/res/drawable/thumb_left.xml
create mode 100644 app/src/main/res/drawable/thumb_right.xml
create mode 100644 app/src/main/res/drawable/volume_off.xml
create mode 100644 app/src/main/res/drawable/volume_up.xml
rename app/src/main/res/menu/{edit_photo_menu.xml => edit_menu.xml} (83%)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 89f1821d..4ba866d4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,8 +28,7 @@
android:theme="@style/AppTheme.ActionBar.Transparent"/>
+ android:exported="false"/>
= mutableMapOf()
+ // Keep track of temporary files to delete them (avoids filling cache super fast with videos)
+ private val tempFiles: ArrayList = ArrayList()
+
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPostCreationBinding.inflate(layoutInflater)
setContentView(binding.root)
+
+
+
user = db.userDao().getActiveUser()
instance = user?.run {
@@ -89,7 +108,7 @@ class PostCreationActivity : BaseActivity() {
intent.clipData?.let { addPossibleImages(it) }
val carousel: ImageCarousel = binding.carousel
- carousel.addData(photoData.map { CarouselItem(it.imageUri, video = it.video) })
+ carousel.addData(photoData.map { CarouselItem(it.imageUri, video = it.video, encodeProgress = null) })
carousel.layoutCarouselCallback = {
if(it){
// Became a carousel
@@ -109,7 +128,7 @@ class PostCreationActivity : BaseActivity() {
// get the description and send the post
binding.postCreationSendButton.setOnClickListener {
- if (validateDescription() && photoData.isNotEmpty()) upload()
+ if (validatePost() && photoData.isNotEmpty()) upload()
}
// Button to retry image upload when it fails
@@ -123,7 +142,7 @@ class PostCreationActivity : BaseActivity() {
}
binding.editPhotoButton.setOnClickListener {
- carousel.currentPosition.takeIf { it != -1 }?.let { currentPosition ->
+ carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition ->
edit(currentPosition)
}
}
@@ -133,27 +152,36 @@ class PostCreationActivity : BaseActivity() {
}
binding.savePhotoButton.setOnClickListener {
- carousel.currentPosition.takeIf { it != -1 }?.let { currentPosition ->
+ carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition ->
savePicture(it, currentPosition)
}
}
binding.removePhotoButton.setOnClickListener {
- carousel.currentPosition.takeIf { it != -1 }?.let { currentPosition ->
+ carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition ->
photoData.removeAt(currentPosition)
- carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription, it.video) })
+ sessionMap[currentPosition]?.let { FFmpegKit.cancel(it) }
+ carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription, it.video, it.videoEncodeProgress) })
binding.addPhotoButton.isEnabled = true
}
}
}
+ override fun onDestroy() {
+ super.onDestroy()
+ FFmpegKit.cancel()
+ tempFiles.forEach {
+ it.delete()
+ }
+ }
+
/**
* Will add as many images as possible to [photoData], from the [clipData], and if
- * ([photoData].size + [clipData].itemCount) > [albumLimit] then it will only add as many images
+ * ([photoData].size + [clipData].itemCount) > [InstanceDatabaseEntity.albumLimit] then it will only add as many images
* as are legal (if any) and a dialog will be shown to the user alerting them of this fact.
*/
- private fun addPossibleImages(clipData: ClipData){
+ private fun addPossibleImages(clipData: ClipData) {
var count = clipData.itemCount
if(count + photoData.size > instance.albumLimit){
AlertDialog.Builder(this).apply {
@@ -168,7 +196,7 @@ class PostCreationActivity : BaseActivity() {
}
for (i in 0 until count) {
clipData.getItemAt(i).uri.let {
- val sizeAndVideoPair: Pair = it.getSizeAndVideoValidate()
+ val sizeAndVideoPair: Pair = it.getSizeAndVideoValidate(photoData.size + 1)
photoData.add(PhotoData(imageUri = it, size = sizeAndVideoPair.first, video = sizeAndVideoPair.second))
}
}
@@ -178,7 +206,7 @@ class PostCreationActivity : BaseActivity() {
* Returns the size of the file of the Uri, and whether it is a video,
* and opens a dialog in case it is too big or in case the file is unsupported.
*/
- private fun Uri.getSizeAndVideoValidate(): Pair {
+ private fun Uri.getSizeAndVideoValidate(editPosition: Int): Pair {
val size: Long =
if (toString().startsWith("content")) {
contentResolver.query(this, null, null, null, null)
@@ -209,7 +237,7 @@ class PostCreationActivity : BaseActivity() {
if (sizeInkBytes > instance.maxPhotoSize || sizeInkBytes > instance.maxVideoSize) {
val maxSize = if (isVideo) instance.maxVideoSize else instance.maxPhotoSize
AlertDialog.Builder(this@PostCreationActivity).apply {
- setMessage(getString(R.string.size_exceeds_instance_limit, photoData.size + 1, sizeInkBytes, maxSize))
+ setMessage(getString(R.string.size_exceeds_instance_limit, editPosition, sizeInkBytes, maxSize))
setNegativeButton(android.R.string.ok) { _, _ -> }
}.show()
}
@@ -221,7 +249,7 @@ class PostCreationActivity : BaseActivity() {
result.data?.clipData?.let {
addPossibleImages(it)
}
- binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription, it.video) })
+ binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription, it.video, it.videoEncodeProgress) })
} else if (result.resultCode != Activity.RESULT_CANCELED) {
Toast.makeText(applicationContext, "Error while adding images", Toast.LENGTH_SHORT).show()
}
@@ -294,7 +322,7 @@ class PostCreationActivity : BaseActivity() {
}
- private fun validateDescription(): Boolean {
+ private fun validatePost(): Boolean {
binding.postTextInputLayout.run {
val content = editText?.length() ?: 0
if (content > counterMaxLength) {
@@ -303,6 +331,13 @@ class PostCreationActivity : BaseActivity() {
return false
}
}
+ if(!photoData.all { it.videoEncodeProgress == null }){
+ AlertDialog.Builder(this).apply {
+ setMessage(R.string.still_encoding)
+ setNegativeButton(android.R.string.ok) { _, _ -> }
+ }.show()
+ return false
+ }
return true
}
@@ -435,20 +470,132 @@ class PostCreationActivity : BaseActivity() {
if (result?.resultCode == Activity.RESULT_OK && result.data != null) {
val position: Int = result.data!!.getIntExtra(PhotoEditActivity.PICTURE_POSITION, 0)
photoData.getOrNull(position)?.apply {
- imageUri = result.data!!.getStringExtra(PhotoEditActivity.PICTURE_URI)!!.toUri()
- val (imageSize, imageVideo) = imageUri.getSizeAndVideoValidate()
- size = imageSize
- video = imageVideo
+ if (video) {
+ val muted: Boolean = result.data!!.getBooleanExtra(VideoEditActivity.MUTED, false)
+ val videoStart: Float? = result.data!!.getFloatExtra(VideoEditActivity.VIDEO_START, -1f).let {
+ if(it == -1f) null else it
+ }
+ val modified: Boolean = result.data!!.getBooleanExtra(VideoEditActivity.MODIFIED, false)
+ val videoEnd: Float? = result.data!!.getFloatExtra(VideoEditActivity.VIDEO_END, -1f).let {
+ if(it == -1f) null else it
+ }
+ if(modified){
+ videoEncodeProgress = 0
+ sessionMap[position]?.let { FFmpegKit.cancel(it) }
+ startEncoding(position, muted, videoStart, videoEnd)
+ }
+ } else {
+ imageUri = result.data!!.getStringExtra(PhotoEditActivity.PICTURE_URI)!!.toUri()
+ val (imageSize, imageVideo) = imageUri.getSizeAndVideoValidate(position)
+ size = imageSize
+ video = imageVideo
+ }
progress = null
uploadId = null
} ?: Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
- binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription, it.video) })
+ binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription, it.video, it.videoEncodeProgress) })
} else if(result?.resultCode != Activity.RESULT_CANCELED){
Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
}
}
+ /**
+ * @param muted should audio tracks be removed in the output
+ * @param videoStart when we want to start the video, in seconds, or null if we
+ * don't want to remove the start
+ * @param videoEnd when we want to end the video, in seconds, or null if we
+ * don't want to remove the end
+ */
+ private fun startEncoding(position: Int, muted: Boolean, videoStart: Float?, videoEnd: Float?) {
+ val originalUri = photoData[position].imageUri
+
+ // Having a meaningful suffix is necessary so that ffmpeg knows what to put in output
+ val suffix = if(originalUri.scheme == "content") {
+ contentResolver.getType(photoData[position].imageUri)?.takeLastWhile { it != '/' }
+ } else {
+ originalUri.toString().takeLastWhile { it != '/' }
+ }
+ val file = File.createTempFile("temp_video", ".$suffix")
+ //val file = File.createTempFile("temp_video", ".webm")
+ tempFiles.add(file)
+ val fileUri = file.toUri()
+ val outputVideoPath = ffmpegSafeUri(fileUri)
+
+ val inputUri = photoData[position].imageUri
+
+ val inputSafePath = ffmpegSafeUri(inputUri)
+
+ val mediaInformation: MediaInformation? = FFprobeKit.getMediaInformation(ffmpegSafeUri(inputUri)).mediaInformation
+ val totalVideoDuration = mediaInformation?.duration?.toFloatOrNull()
+
+ val mutedString = if(muted) "-an" else ""
+ val startString = if(videoStart != null) "-ss $videoStart" else ""
+
+ val endString = if(videoEnd != null) "-to ${videoEnd - (videoStart ?: 0f)}" else ""
+
+ val session: FFmpegSession = FFmpegKit.executeAsync("$startString -i $inputSafePath $endString -c copy $mutedString -y $outputVideoPath",
+ //val session: FFmpegSession = FFmpegKit.executeAsync("$startString -i $inputSafePath $endString -c:v libvpx-vp9 -c:a copy -an -y $outputVideoPath",
+ { session ->
+ val returnCode = session.returnCode
+ if (ReturnCode.isSuccess(returnCode)) {
+ fun successResult() {
+ // Hide progress indicator in carousel
+ binding.carousel.updateProgress(null, position, false)
+ val (imageSize, imageVideo) = outputVideoPath.toUri().let {
+ photoData[position].imageUri = it
+ it.getSizeAndVideoValidate(position)
+ }
+ photoData[position].videoEncodeProgress = null
+ photoData[position].size = imageSize
+ binding.carousel.addData(photoData.map {
+ CarouselItem(it.imageUri,
+ it.imageDescription,
+ it.video,
+ it.videoEncodeProgress)
+ })
+ }
+
+ val post = resultHandler.post {
+ successResult()
+ }
+ if(!post) {
+ Log.e(TAG, "Failed to post changes, trying to recover in 100ms")
+ resultHandler.postDelayed({successResult()}, 100)
+ }
+ Log.d(TAG, "Encode completed successfully in ${session.duration} milliseconds")
+ } else {
+ resultHandler.post {
+ binding.carousel.updateProgress(null, position, error = true)
+ photoData[position].videoEncodeProgress = null
+ }
+ Log.e(TAG, "Encode failed with state ${session.state} and rc $returnCode.${session.failStackTrace}")
+ }
+ },
+ { log -> Log.d("PostCreationActivityEncoding", log.message) }
+ ) { statistics: Statistics? ->
+
+ val timeInMilliseconds: Int? = statistics?.time
+ timeInMilliseconds?.let {
+ if (timeInMilliseconds > 0) {
+ val completePercentage = totalVideoDuration?.let {
+ val newTotalDuration = it - (videoStart ?: 0f) - (it - (videoEnd ?: it))
+ timeInMilliseconds / (10*newTotalDuration)
+ }
+ resultHandler.post {
+ completePercentage?.let {
+ val rounded = it.roundToInt()
+ photoData[position].videoEncodeProgress = rounded
+ binding.carousel.updateProgress(rounded, position, false)
+ }
+ }
+ Log.d(TAG, "Encoding video: %$completePercentage.")
+ }
+ }
+ }
+ sessionMap[position] = session.sessionId
+ }
+
private fun edit(position: Int) {
val intent = Intent(
this,
diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt
new file mode 100644
index 00000000..8b7f1d1b
--- /dev/null
+++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt
@@ -0,0 +1,30 @@
+package org.pixeldroid.app.postCreation
+
+import android.content.ClipData
+import android.os.Bundle
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+
+class PostCreationViewModel : ViewModel() {
+ private val photoData: MutableLiveData> by lazy {
+ MutableLiveData>().also {
+ loadUsers()
+ }
+ }
+
+ fun getUsers(): LiveData> {
+ return photoData
+ }
+
+ private fun loadUsers() {
+ // Do an asynchronous operation to fetch users.
+ }
+}
+class PostCreationViewModelFactory(val bundle: ClipData? = null) : ViewModelProvider.Factory {
+ override fun create(modelClass: Class): T {
+ return modelClass.getConstructor(ClipData::class.java).newInstance(bundle)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/carousel/CarouselItem.kt b/app/src/main/java/org/pixeldroid/app/postCreation/carousel/CarouselItem.kt
index 37dfda86..bbd4b8c7 100644
--- a/app/src/main/java/org/pixeldroid/app/postCreation/carousel/CarouselItem.kt
+++ b/app/src/main/java/org/pixeldroid/app/postCreation/carousel/CarouselItem.kt
@@ -5,5 +5,6 @@ import android.net.Uri
data class CarouselItem constructor(
val imageUrl: Uri,
val caption: String? = null,
- val video: Boolean
+ val video: Boolean,
+ var encodeProgress: Int?
)
\ No newline at end of file
diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/carousel/ImageCarousel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/carousel/ImageCarousel.kt
index 5b628b3f..bb2473aa 100644
--- a/app/src/main/java/org/pixeldroid/app/postCreation/carousel/ImageCarousel.kt
+++ b/app/src/main/java/org/pixeldroid/app/postCreation/carousel/ImageCarousel.kt
@@ -69,7 +69,7 @@ class ImageCarousel(
private var isBuiltInIndicator = false
- private var data: List? = null
+ private var data: MutableList? = null
var onItemClickListener: OnItemClickListener? = this
set(value) {
@@ -88,28 +88,34 @@ class ImageCarousel(
/**
* Get or set current item position
*/
- var currentPosition = -1
+ var currentPosition = RecyclerView.NO_POSITION
get() {
return snapHelper.getSnapPosition(recyclerView.layoutManager)
}
set(value) {
- val position = when {
- value >= data?.size ?: 0 -> {
- -1
- }
- value < 0 -> {
- -1
- }
- else -> {
- value
- }
+ val position = when (value) {
+ !in 0..((data?.size?.minus(1)) ?: 0) -> RecyclerView.NO_POSITION
+ else -> value
}
- field = position
+ if (position != RecyclerView.NO_POSITION && field != position) {
+ val thisProgress = data?.get(position)?.encodeProgress
+ if (thisProgress != null) {
+ binding.encodeProgress.visibility = VISIBLE
+ binding.encodeInfoText.visibility = VISIBLE
+ binding.encodeInfoText.text =
+ context.getString(R.string.encode_progress).format(thisProgress)
+ binding.encodeProgress.progress = thisProgress
+ } else {
+ binding.encodeProgress.visibility = INVISIBLE
+ binding.encodeInfoText.visibility = INVISIBLE
+ }
+ } else binding.encodeProgress.visibility = INVISIBLE
- if (position != -1) {
+ if (position != RecyclerView.NO_POSITION && recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE) {
recyclerView.smoothScrollToPosition(position)
}
+ field = position
}
/**
@@ -450,10 +456,9 @@ class ImageCarousel(
private fun initListeners() {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+ val position = currentPosition
if (showCaption) {
- val position = snapHelper.getSnapPosition(recyclerView.layoutManager)
-
if (position >= 0) {
val dataItem = adapter?.getItem(position)
@@ -469,6 +474,8 @@ class ImageCarousel(
}
}
+ if(dx !=0 || dy != 0) currentPosition = position
+
onScrollListener?.onScrolled(recyclerView, dx, dy)
}
@@ -561,12 +568,37 @@ class ImageCarousel(
adapter?.apply {
addAll(data)
- this@ImageCarousel.data = data
+ this@ImageCarousel.data = data.toMutableList()
initOnScrollStateChange()
}
}
+ fun updateProgress(progress: Int?, position: Int, error: Boolean){
+ data?.get(position)?.encodeProgress = progress
+ if(currentPosition == position) {
+ if (progress == null) {
+ binding.encodeProgress.visibility = INVISIBLE
+ binding.encodeInfoText.visibility = VISIBLE
+ if(error){
+ binding.encodeInfoText.setText(R.string.encode_error)
+ binding.encodeInfoText.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(context, R.drawable.error),
+ null, null, null)
+
+ } else {
+ binding.encodeInfoText.setText(R.string.encode_success)
+ binding.encodeInfoText.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(context, R.drawable.check_circle_24),
+ null, null, null)
+ }
+ } else {
+ binding.encodeProgress.visibility = VISIBLE
+ binding.encodeProgress.progress = progress
+ binding.encodeInfoText.visibility = VISIBLE
+ binding.encodeInfoText.text = context.getString(R.string.encode_progress).format(progress)
+ }
+ }
+ }
+
/**
* Goto previous item.
*/
diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/PhotoEditActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/PhotoEditActivity.kt
index d32789f9..04566a8b 100644
--- a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/PhotoEditActivity.kt
+++ b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/PhotoEditActivity.kt
@@ -148,7 +148,7 @@ class PhotoEditActivity : BaseActivity() {
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.edit_photo_menu, menu)
+ menuInflater.inflate(R.menu.edit_menu, menu)
return true
}
@@ -191,8 +191,8 @@ class PhotoEditActivity : BaseActivity() {
}
}
- return super.onOptionsItemSelected(item)
-}
+ return super.onOptionsItemSelected(item)
+ }
fun onFilterSelected(filter: Filter) {
filteredImage = compressedOriginalImage!!.copy(BITMAP_CONFIG, true)
diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt
index 53c3240d..11d0526a 100644
--- a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt
+++ b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt
@@ -1,57 +1,279 @@
package org.pixeldroid.app.postCreation.photoEdit
+import android.app.Activity
+import android.app.AlertDialog
+import android.content.Intent
+import android.media.AudioManager
import android.net.Uri
import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.text.format.DateUtils
import android.util.Log
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.SeekBar
+import android.widget.SeekBar.OnSeekBarChangeListener
import androidx.core.net.toUri
+import androidx.core.os.HandlerCompat
+import androidx.media.AudioAttributesCompat
+import androidx.media2.common.MediaMetadata
+import androidx.media2.common.UriMediaItem
+import androidx.media2.player.MediaPlayer
import com.arthenica.ffmpegkit.*
-import com.arthenica.ffmpegkit.MediaInformation.KEY_DURATION
import com.bumptech.glide.Glide
+import com.google.android.material.slider.RangeSlider
+import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityVideoEditBinding
+import org.pixeldroid.app.postCreation.PostCreationActivity
+import org.pixeldroid.app.postCreation.carousel.dpToPx
import org.pixeldroid.app.utils.BaseActivity
+import org.pixeldroid.app.utils.ffmpegSafeUri
import java.io.File
+import java.text.NumberFormat
+import java.time.format.DateTimeFormatter
+import java.util.*
+import kotlin.collections.ArrayList
class VideoEditActivity : BaseActivity() {
+
+ private lateinit var mediaPlayer: MediaPlayer
+ private var videoPosition: Int = -1
+ private lateinit var binding: ActivityVideoEditBinding
+ // Map photoData indexes to FFmpeg Session IDs
+ private val sessionList: ArrayList = arrayListOf()
+ private val tempFiles: ArrayList = ArrayList()
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val binding = ActivityVideoEditBinding.inflate(layoutInflater)
-
+ binding = ActivityVideoEditBinding.inflate(layoutInflater)
setContentView(binding.root)
- val uri = intent.getParcelableExtra(PhotoEditActivity.PICTURE_URI) as Uri?
- val videoPosition = intent.getIntExtra(PhotoEditActivity.PICTURE_POSITION, -1)
- val inputVideoPath =if(uri.toString().startsWith("content://")) FFmpegKitConfig.getSafParameterForRead(this, uri) else uri.toString()
- val inputVideoPath2 =if(uri.toString().startsWith("content://")) FFmpegKitConfig.getSafParameterForRead(this, uri) else uri.toString()
+ supportActionBar?.setTitle(R.string.toolbar_title_edit)
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ supportActionBar?.setHomeButtonEnabled(true)
+
+
+ binding.videoRangeSeekBar.setCustomThumbDrawablesForValues(R.drawable.thumb_left,R.drawable.double_circle,R.drawable.thumb_right)
+ binding.videoRangeSeekBar.thumbRadius = 20.dpToPx(this)
+
+
+ val resultHandler: Handler = HandlerCompat.createAsync(Looper.getMainLooper())
+
+ val uri = intent.getParcelableExtra(PhotoEditActivity.PICTURE_URI)!!
+ videoPosition = intent.getIntExtra(PhotoEditActivity.PICTURE_POSITION, -1)
+
+ val inputVideoPath = ffmpegSafeUri(uri)
val mediaInformation: MediaInformation? = FFprobeKit.getMediaInformation(inputVideoPath).mediaInformation
- val duration: Long? = mediaInformation?.getNumberProperty(KEY_DURATION)
-
- val file = File.createTempFile("temp_img", ".png").toUri()
-
- val outputImagePath =if(file.toString().startsWith("content://")) FFmpegKitConfig.getSafParameterForWrite(this, file) else file.toString()
-
- val session = FFmpegKit.execute(
- "-i $inputVideoPath2 -filter_complex \"select='not(mod(n,1000))',scale=240:-1,tile=layout=4x1\" -vframes 1 -q:v 2 -y $outputImagePath"
- )
- if (ReturnCode.isSuccess(session.returnCode)) {
- Glide.with(this).load(file).into(binding.thumbnails)
- // SUCCESS
- } else if (ReturnCode.isCancel(session.returnCode)) {
-
- // CANCEL
- } else {
-
- // FAILURE
- Log.d("VideoEditActivity",
- String.format("Command failed with state %s and rc %s.%s",
- session.state,
- session.returnCode,
- session.failStackTrace))
+ binding.muter.setOnClickListener {
+ binding.muter.isSelected = !binding.muter.isSelected
}
+ //Duration in seconds, or null
+ val duration: Float? = mediaInformation?.duration?.toFloatOrNull()
+
+ binding.videoRangeSeekBar.valueFrom = 0f
+ binding.videoRangeSeekBar.valueTo = duration ?: 100f
+ binding.videoRangeSeekBar.values = listOf(0f,(duration?: 100f) / 2, duration ?: 100f)
+
+
+ val mediaItem: UriMediaItem = UriMediaItem.Builder(uri).build()
+ mediaItem.metadata = MediaMetadata.Builder()
+ .putString(MediaMetadata.METADATA_KEY_TITLE, "")
+ .build()
+
+ mediaPlayer = MediaPlayer(this)
+ mediaPlayer.setMediaItem(mediaItem)
+
+ //binding.videoView.mediaControlView?.setMediaController()
+
+ // Configure audio
+ mediaPlayer.setAudioAttributes(AudioAttributesCompat.Builder()
+ .setLegacyStreamType(AudioManager.STREAM_MUSIC)
+ .setUsage(AudioAttributesCompat.USAGE_MEDIA)
+ .setContentType(AudioAttributesCompat.CONTENT_TYPE_MOVIE)
+ .build()
+ )
+
+ findViewById(R.id.progress_bar)?.visibility = View.GONE
+
+ mediaPlayer.prepare()
+
+ binding.muter.setOnClickListener {
+ if(!binding.muter.isSelected) mediaPlayer.playerVolume = 0f
+ else mediaPlayer.playerVolume = 1f
+ binding.muter.isSelected = !binding.muter.isSelected
+ }
+
+ binding.videoView.setPlayer(mediaPlayer)
+
+ mediaPlayer.seekTo((binding.videoRangeSeekBar.values[1]*1000).toLong())
+
+ object : Runnable {
+ override fun run() {
+ val getCurrent = mediaPlayer.currentPosition / 1000f
+ if(getCurrent >= binding.videoRangeSeekBar.values[0] && getCurrent <= binding.videoRangeSeekBar.values[2] ) {
+ binding.videoRangeSeekBar.values = listOf(binding.videoRangeSeekBar.values[0],getCurrent, binding.videoRangeSeekBar.values[2])
+ }
+ Handler(Looper.getMainLooper()).postDelayed(this, 1000)
+ }
+ }.run()
+
+ binding.videoRangeSeekBar.addOnChangeListener { rangeSlider: RangeSlider, value, fromUser ->
+ // Responds to when the middle slider's value is changed
+ if(fromUser && value != rangeSlider.values[0] && value != rangeSlider.values[2]) {
+ mediaPlayer.seekTo((rangeSlider.values[1]*1000).toLong())
+ }
+ }
+
+ binding.videoRangeSeekBar.setLabelFormatter { value: Float ->
+ DateUtils.formatElapsedTime(value.toLong())
+ }
+
+
+ val thumbInterval: Float? = duration?.div(7)
+
+ thumbInterval?.let {
+ thumbnail(uri, resultHandler, binding.thumbnail1, it)
+ thumbnail(uri, resultHandler, binding.thumbnail2, it.times(2))
+ thumbnail(uri, resultHandler, binding.thumbnail3, it.times(3))
+ thumbnail(uri, resultHandler, binding.thumbnail4, it.times(4))
+ thumbnail(uri, resultHandler, binding.thumbnail5, it.times(5))
+ thumbnail(uri, resultHandler, binding.thumbnail6, it.times(6))
+ thumbnail(uri, resultHandler, binding.thumbnail7, it.times(7))
+ }
+
+
}
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.edit_menu, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+
+ when(item.itemId) {
+ android.R.id.home -> onBackPressed()
+ R.id.action_save -> {
+ returnWithValues()
+ }
+ R.id.action_reset -> {
+ resetControls()
+ }
+ }
+
+ return super.onOptionsItemSelected(item)
+ }
+
+ override fun onBackPressed() {
+ if (noEdits()) super.onBackPressed()
+ else {
+ val builder = AlertDialog.Builder(this)
+ builder.apply {
+ setMessage(R.string.save_before_returning)
+ setPositiveButton(android.R.string.ok) { _, _ ->
+ returnWithValues()
+ }
+ setNegativeButton(R.string.no_cancel_edit) { _, _ ->
+ super.onBackPressed()
+ }
+ }
+ // Create the AlertDialog
+ builder.show()
+ }
+ }
+
+ private fun noEdits(): Boolean {
+ val videoPositions = binding.videoRangeSeekBar.values.let {
+ it[0] == 0f && it[2] == binding.videoRangeSeekBar.valueTo
+ }
+ val muted = binding.muter.isSelected
+ return !muted && videoPositions
+ }
+
+
+ private fun returnWithValues() {
+ val intent = Intent(this, PostCreationActivity::class.java)
+ .apply {
+ putExtra(PhotoEditActivity.PICTURE_POSITION, videoPosition)
+ putExtra(MUTED, binding.muter.isSelected)
+ putExtra(MODIFIED, !noEdits())
+ putExtra(VIDEO_START, binding.videoRangeSeekBar.values.first())
+ putExtra(VIDEO_END, binding.videoRangeSeekBar.values[2])
+ addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
+ }
+
+ setResult(Activity.RESULT_OK, intent)
+ finish()
+ }
+
+ private fun resetControls() {
+ binding.videoRangeSeekBar.values = listOf(0f, binding.videoRangeSeekBar.valueTo/2, binding.videoRangeSeekBar.valueTo)
+ binding.muter.isSelected = false
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ sessionList.forEach {
+ FFmpegKit.cancel(it)
+ }
+ tempFiles.forEach{
+ it.delete()
+ }
+ mediaPlayer.close()
+ }
+
+ private fun thumbnail(
+ inputUri: Uri?,
+ resultHandler: Handler,
+ thumbnail: ImageView,
+ thumbTime: Float,
+ ) {
+ val file = File.createTempFile("temp_img", ".bmp")
+ tempFiles.add(file)
+ val fileUri = file.toUri()
+ val inputSafePath = ffmpegSafeUri(inputUri)
+
+ val outputImagePath =
+ if(fileUri.toString().startsWith("content://"))
+ FFmpegKitConfig.getSafParameterForWrite(this, fileUri)
+ else fileUri.toString()
+ val session = FFmpegKit.executeAsync(
+ "-noaccurate_seek -ss $thumbTime -i $inputSafePath -vf scale=${thumbnail.width}:${thumbnail.height} -frames:v 1 -f image2 -y $outputImagePath",
+ { session ->
+ val state = session.state
+ val returnCode = session.returnCode
+
+ if (ReturnCode.isSuccess(returnCode)) {
+ // SUCCESS
+ resultHandler.post {
+ if(!this.isFinishing)
+ Glide.with(this).load(outputImagePath).centerCrop().into(thumbnail)
+ }
+ }
+ // CALLED WHEN SESSION IS EXECUTED
+ Log.d("VideoEditActivity", "FFmpeg process exited with state $state and rc $returnCode.${session.failStackTrace}")
+ },
+ {/* CALLED WHEN SESSION PRINTS LOGS */ }) { /*CALLED WHEN SESSION GENERATES STATISTICS*/ }
+ sessionList.add(session.sessionId)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ mediaPlayer.pause()
+ }
+
companion object {
const val VIDEO_TAG = "VideoEditTag"
+ const val MUTED = "VideoEditMutedTag"
+ const val VIDEO_START = "VideoEditVideoStartTag"
+ const val VIDEO_END = "VideoEditVideoEndTag"
+ const val MODIFIED = "VideoEditModifiedTag"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/pixeldroid/app/utils/Utils.kt b/app/src/main/java/org/pixeldroid/app/utils/Utils.kt
index c724c15b..b2ef5187 100644
--- a/app/src/main/java/org/pixeldroid/app/utils/Utils.kt
+++ b/app/src/main/java/org/pixeldroid/app/utils/Utils.kt
@@ -19,6 +19,7 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import com.arthenica.ffmpegkit.FFmpegKitConfig
import okhttp3.HttpUrl
import org.pixeldroid.app.R
import kotlin.properties.ReadWriteProperty
@@ -65,6 +66,12 @@ fun normalizeDomain(domain: String): String {
.trim(Char::isWhitespace)
}
+fun Context.ffmpegSafeUri(inputUri: Uri?): String =
+ if (inputUri?.scheme == "content")
+ FFmpegKitConfig.getSafParameterForRead(this, inputUri)
+ else inputUri.toString()
+
+
fun bitmapFromUri(contentResolver: ContentResolver, uri: Uri?): Bitmap =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ImageDecoder
@@ -107,7 +114,7 @@ fun Bitmap.flip(horizontal: Boolean, vertical: Boolean): Bitmap {
return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
}
-fun BaseActivity.openUrl(url: String): Boolean{
+fun BaseActivity.openUrl(url: String): Boolean {
val intent = CustomTabsIntent.Builder().build()
diff --git a/app/src/main/res/drawable/double_circle.xml b/app/src/main/res/drawable/double_circle.xml
new file mode 100644
index 00000000..907d1235
--- /dev/null
+++ b/app/src/main/res/drawable/double_circle.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/error.xml b/app/src/main/res/drawable/error.xml
new file mode 100644
index 00000000..17575711
--- /dev/null
+++ b/app/src/main/res/drawable/error.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/selector_mute.xml b/app/src/main/res/drawable/selector_mute.xml
new file mode 100644
index 00000000..7103cc0c
--- /dev/null
+++ b/app/src/main/res/drawable/selector_mute.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/thumb_left.xml b/app/src/main/res/drawable/thumb_left.xml
new file mode 100644
index 00000000..d6c10419
--- /dev/null
+++ b/app/src/main/res/drawable/thumb_left.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/thumb_right.xml b/app/src/main/res/drawable/thumb_right.xml
new file mode 100644
index 00000000..6b5f6222
--- /dev/null
+++ b/app/src/main/res/drawable/thumb_right.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/volume_off.xml b/app/src/main/res/drawable/volume_off.xml
new file mode 100644
index 00000000..b71ede34
--- /dev/null
+++ b/app/src/main/res/drawable/volume_off.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/volume_up.xml b/app/src/main/res/drawable/volume_up.xml
new file mode 100644
index 00000000..836cad86
--- /dev/null
+++ b/app/src/main/res/drawable/volume_up.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_video_edit.xml b/app/src/main/res/layout/activity_video_edit.xml
index d7c9300f..fe3feb98 100644
--- a/app/src/main/res/layout/activity_video_edit.xml
+++ b/app/src/main/res/layout/activity_video_edit.xml
@@ -1,15 +1,112 @@
+ android:background="@android:color/black"
+ android:scrollbarThumbHorizontal="@drawable/thumb_left">
+
+
+
+
+
+
+
+
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toLeftOf="@+id/thumbnail2" />
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/image_carousel.xml b/app/src/main/res/layout/image_carousel.xml
index 3bbd86fe..dee9a908 100644
--- a/app/src/main/res/layout/image_carousel.xml
+++ b/app/src/main/res/layout/image_carousel.xml
@@ -162,4 +162,30 @@
app:layout_constraintTop_toTopOf="@+id/indicator"
tools:visibility="visible" />
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/edit_photo_menu.xml b/app/src/main/res/menu/edit_menu.xml
similarity index 83%
rename from app/src/main/res/menu/edit_photo_menu.xml
rename to app/src/main/res/menu/edit_menu.xml
index 31194bd7..4730db3b 100644
--- a/app/src/main/res/menu/edit_photo_menu.xml
+++ b/app/src/main/res/menu/edit_menu.xml
@@ -7,13 +7,13 @@
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0e756cb5..7a9f17e5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -263,4 +263,11 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"
Play video
Video editing is not yet supported
Reel showing thumbnails of the video you are editing
+ RESET
+ SAVE
+ Error encoding
+ Encode success!
+ Encode %1$d%%
+ Select what to keep of the video
+ One or more videos are still encoding. Wait for them to finish before uploading
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 02b00d99..8e59e764 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -4,4 +4,4 @@ distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
-distributionSha256Sum=f581709a9c35e9cb92e16f585d2c4bc99b2b1a5f85d2badbd3dc6bff59e1e6dd
+distributionSha256Sum=b586e04868a22fd817c8971330fec37e298f3242eb85c374181b12d637f80302
From 5c221e004ddb74401e720411ba6978c4e18cad20 Mon Sep 17 00:00:00 2001
From: Matthieu <24-artectrex@users.noreply.shinice.net>
Date: Sun, 19 Jun 2022 13:02:05 +0200
Subject: [PATCH 3/5] huge refactor of PostCreation to use ViewModel
---
app/src/main/AndroidManifest.xml | 4 +-
.../app/postCreation/PostCreationActivity.kt | 386 ++++-----------
.../app/postCreation/PostCreationViewModel.kt | 440 +++++++++++++++++-
.../postCreation/carousel/ImageCarousel.kt | 37 +-
.../photoEdit/VideoEditActivity.kt | 6 -
.../app/utils/di/ApplicationComponent.kt | 2 +
6 files changed, 537 insertions(+), 338 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4ba866d4..81d102bb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -44,9 +44,7 @@
+ android:theme="@style/AppTheme.NoActionBar">
diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt
index 3e4890e9..63cd86af 100644
--- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt
+++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt
@@ -7,7 +7,6 @@ import android.media.MediaScannerConnection
import android.net.Uri
import android.os.*
import android.provider.MediaStore
-import android.provider.OpenableColumns
import android.util.Log
import android.view.View
import android.view.View.INVISIBLE
@@ -20,14 +19,19 @@ import androidx.activity.viewModels
import androidx.core.net.toFile
import androidx.core.net.toUri
import androidx.core.os.HandlerCompat
-import androidx.lifecycle.Observer
+import androidx.core.widget.addTextChangedListener
+import androidx.core.widget.doAfterTextChanged
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView
import com.arthenica.ffmpegkit.*
import com.google.android.material.snackbar.Snackbar
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
import okhttp3.MultipartBody
import org.pixeldroid.app.MainActivity
import org.pixeldroid.app.R
@@ -48,16 +52,11 @@ import java.io.FileNotFoundException
import java.io.IOException
import java.io.OutputStream
import java.text.SimpleDateFormat
-
-import kotlin.math.ceil
-import com.arthenica.ffmpegkit.FFprobeKit
import java.util.*
-import kotlin.collections.ArrayList
-
import kotlin.math.roundToInt
-private const val TAG = "Post Creation Activity"
+const val TAG = "Post Creation Activity"
data class PhotoData(
var imageUri: Uri,
@@ -74,16 +73,11 @@ class PostCreationActivity : BaseActivity() {
private var user: UserDatabaseEntity? = null
private lateinit var instance: InstanceDatabaseEntity
- private val photoData: ArrayList = ArrayList()
-
private lateinit var binding: ActivityPostCreationBinding
private val resultHandler: Handler = HandlerCompat.createAsync(Looper.getMainLooper())
- // Map photoData indexes to FFmpeg Session IDs
- private val sessionMap: MutableMap = mutableMapOf()
- // Keep track of temporary files to delete them (avoids filling cache super fast with videos)
- private val tempFiles: ArrayList = ArrayList()
+ private lateinit var model: PostCreationViewModel
override fun onCreate(savedInstanceState: Bundle?) {
@@ -91,9 +85,6 @@ class PostCreationActivity : BaseActivity() {
binding = ActivityPostCreationBinding.inflate(layoutInflater)
setContentView(binding.root)
-
-
-
user = db.userDao().getActiveUser()
instance = user?.run {
@@ -102,47 +93,87 @@ class PostCreationActivity : BaseActivity() {
}
} ?: InstanceDatabaseEntity("", "")
- binding.postTextInputLayout.counterMaxLength = instance.maxStatusChars
+ val _model: PostCreationViewModel by viewModels { PostCreationViewModelFactory(application, intent.clipData!!, instance) }
+ model = _model
- // get image URIs
- intent.clipData?.let { addPossibleImages(it) }
+ model.getPhotoData().observe(this) { newPhotoData ->
+ // update UI
+ binding.carousel.addData(
+ newPhotoData.map {
+ CarouselItem(it.imageUri, it.imageDescription, it.video, it.videoEncodeProgress)
+ }
+ )
+ }
- val carousel: ImageCarousel = binding.carousel
- carousel.addData(photoData.map { CarouselItem(it.imageUri, video = it.video, encodeProgress = null) })
- carousel.layoutCarouselCallback = {
- if(it){
- // Became a carousel
- binding.toolbarPostCreation.visibility = VISIBLE
- } else {
- // Became a grid
- binding.toolbarPostCreation.visibility = INVISIBLE
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ model.uiState.collect { uiState ->
+ uiState.userMessage?.let {
+ AlertDialog.Builder(binding.root.context).apply {
+ setMessage(it)
+ setNegativeButton(android.R.string.ok) { _, _ -> }
+ }.show()
+
+ // Notify the ViewModel the message is displayed
+ model.userMessageShown()
+ }
+ binding.addPhotoButton.isEnabled = uiState.addPhotoButtonEnabled
+ enableButton(uiState.postCreationSendButtonEnabled)
+ binding.uploadProgressBar.visibility = if(uiState.uploadProgressBarVisible) VISIBLE else INVISIBLE
+ binding.uploadProgressBar.progress = uiState.uploadProgress
+ binding.uploadCompletedTextview.visibility = if(uiState.uploadCompletedTextviewVisible) VISIBLE else INVISIBLE
+ binding.removePhotoButton.isEnabled = uiState.removePhotoButtonEnabled
+ binding.editPhotoButton.isEnabled = uiState.editPhotoButtonEnabled
+ binding.uploadError.visibility = if(uiState.uploadErrorVisible) VISIBLE else INVISIBLE
+ binding.uploadErrorTextExplanation.visibility = if(uiState.uploadErrorExplanationVisible) VISIBLE else INVISIBLE
+
+ binding.toolbarPostCreation.visibility = if(uiState.isCarousel) VISIBLE else INVISIBLE
+ binding.carousel.layoutCarousel = uiState.isCarousel
+
+
+ binding.uploadErrorTextExplanation.text = uiState.uploadErrorExplanationText
+
+ uiState.newEncodingJobPosition?.let { position ->
+ uiState.newEncodingJobMuted?.let { muted ->
+ uiState.newEncodingJobVideoStart?.let { videoStart ->
+ uiState.newEncodingJobVideoEnd?.let { videoEnd ->
+ startEncoding(position, muted, videoStart, videoEnd)
+ model.encodingStarted()
+ }
+ }
+ }
+ }
+ }
}
}
- carousel.maxEntries = instance.albumLimit
- carousel.addPhotoButtonCallback = {
- addPhoto()
- }
- carousel.updateDescriptionCallback = { position: Int, description: String ->
- photoData.getOrNull(position)?.imageDescription = description
+ binding.newPostDescriptionInputField.doAfterTextChanged {
+ model.newPostDescriptionChanged(binding.newPostDescriptionInputField.text)
}
+ binding.postTextInputLayout.counterMaxLength = instance.maxStatusChars
+ binding.carousel.apply {
+ layoutCarouselCallback = { model.becameCarousel(it)}
+ maxEntries = instance.albumLimit
+ addPhotoButtonCallback = {
+ addPhoto()
+ }
+ updateDescriptionCallback = { position: Int, description: String ->
+ model.updateDescription(position, description)
+ }
+ }
// get the description and send the post
binding.postCreationSendButton.setOnClickListener {
- if (validatePost() && photoData.isNotEmpty()) upload()
+ if (validatePost() && model.isNotEmpty()) model.upload()
}
// Button to retry image upload when it fails
binding.retryUploadButton.setOnClickListener {
- binding.uploadError.visibility = View.GONE
- photoData.forEach {
- it.uploadId = null
- it.progress = null
- }
- upload()
+ model.resetUploadStatus()
+ model.upload()
}
binding.editPhotoButton.setOnClickListener {
- carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition ->
+ binding.carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition ->
edit(currentPosition)
}
}
@@ -152,104 +183,25 @@ class PostCreationActivity : BaseActivity() {
}
binding.savePhotoButton.setOnClickListener {
- carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition ->
+ binding.carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition ->
savePicture(it, currentPosition)
}
}
binding.removePhotoButton.setOnClickListener {
- carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition ->
- photoData.removeAt(currentPosition)
- sessionMap[currentPosition]?.let { FFmpegKit.cancel(it) }
- carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription, it.video, it.videoEncodeProgress) })
- binding.addPhotoButton.isEnabled = true
+ binding.carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition ->
+ model.removeAt(currentPosition)
+ model.cancelEncode(currentPosition)
}
}
}
- override fun onDestroy() {
- super.onDestroy()
- FFmpegKit.cancel()
- tempFiles.forEach {
- it.delete()
- }
- }
-
- /**
- * Will add as many images as possible to [photoData], from the [clipData], and if
- * ([photoData].size + [clipData].itemCount) > [InstanceDatabaseEntity.albumLimit] then it will only add as many images
- * as are legal (if any) and a dialog will be shown to the user alerting them of this fact.
- */
- private fun addPossibleImages(clipData: ClipData) {
- var count = clipData.itemCount
- if(count + photoData.size > instance.albumLimit){
- AlertDialog.Builder(this).apply {
- setMessage(getString(R.string.total_exceeds_album_limit).format(instance.albumLimit))
- setNegativeButton(android.R.string.ok) { _, _ -> }
- }.show()
- count = count.coerceAtMost(instance.albumLimit - photoData.size)
- }
- if (count + photoData.size >= instance.albumLimit) {
- // Disable buttons to add more images
- binding.addPhotoButton.isEnabled = false
- }
- for (i in 0 until count) {
- clipData.getItemAt(i).uri.let {
- val sizeAndVideoPair: Pair = it.getSizeAndVideoValidate(photoData.size + 1)
- photoData.add(PhotoData(imageUri = it, size = sizeAndVideoPair.first, video = sizeAndVideoPair.second))
- }
- }
- }
-
- /**
- * Returns the size of the file of the Uri, and whether it is a video,
- * and opens a dialog in case it is too big or in case the file is unsupported.
- */
- private fun Uri.getSizeAndVideoValidate(editPosition: Int): Pair {
- val size: Long =
- if (toString().startsWith("content")) {
- contentResolver.query(this, null, null, null, null)
- ?.use { cursor ->
- /* Get the column indexes of the data in the Cursor,
- * move to the first row in the Cursor, get the data,
- * and display it.
- */
- val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
- cursor.moveToFirst()
- cursor.getLong(sizeIndex)
- } ?: 0
- } else {
- toFile().length()
- }
-
- val sizeInkBytes = ceil(size.toDouble() / 1000).toLong()
- val type = contentResolver.getType(this)
- val isVideo = type?.startsWith("video/") == true
-
- if(isVideo && !instance.videoEnabled){
- AlertDialog.Builder(this@PostCreationActivity).apply {
- setMessage(R.string.video_not_supported)
- setNegativeButton(android.R.string.ok) { _, _ -> }
- }.show()
- }
-
- if (sizeInkBytes > instance.maxPhotoSize || sizeInkBytes > instance.maxVideoSize) {
- val maxSize = if (isVideo) instance.maxVideoSize else instance.maxPhotoSize
- AlertDialog.Builder(this@PostCreationActivity).apply {
- setMessage(getString(R.string.size_exceeds_instance_limit, editPosition, sizeInkBytes, maxSize))
- setNegativeButton(android.R.string.ok) { _, _ -> }
- }.show()
- }
- return Pair(size, isVideo)
- }
-
private val addPhotoResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK && result.data?.clipData != null) {
result.data?.clipData?.let {
- addPossibleImages(it)
+ model.setImages(model.addPossibleImages(it))
}
- binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription, it.video, it.videoEncodeProgress) })
} else if (result.resultCode != Activity.RESULT_CANCELED) {
Toast.makeText(applicationContext, "Error while adding images", Toast.LENGTH_SHORT).show()
}
@@ -268,7 +220,7 @@ class PostCreationActivity : BaseActivity() {
val outputStream: OutputStream = pair.first
val path: String = pair.second
- contentResolver.openInputStream(photoData[currentPosition].imageUri)!!.use { input ->
+ contentResolver.openInputStream(model.getPhotoData().value!![currentPosition].imageUri)!!.use { input ->
outputStream.use { output ->
input.copyTo(output)
}
@@ -331,7 +283,7 @@ class PostCreationActivity : BaseActivity() {
return false
}
}
- if(!photoData.all { it.videoEncodeProgress == null }){
+ if(model.getPhotoData().value?.all { it.videoEncodeProgress == null } == false){
AlertDialog.Builder(this).apply {
setMessage(R.string.still_encoding)
setNegativeButton(android.R.string.ok) { _, _ -> }
@@ -341,118 +293,6 @@ class PostCreationActivity : BaseActivity() {
return true
}
- /**
- * Uploads the images that are in the [photoData] array.
- * Keeps track of them in the [PhotoData.progress] (for the upload progress), and the
- * [PhotoData.uploadId] (for the list of ids of the uploads).
- */
- private fun upload() {
- enableButton(false)
- binding.uploadProgressBar.visibility = VISIBLE
- binding.uploadCompletedTextview.visibility = INVISIBLE
- binding.removePhotoButton.isEnabled = false
- binding.editPhotoButton.isEnabled = false
- binding.addPhotoButton.isEnabled = false
-
- for (data: PhotoData in photoData) {
- val imageUri = data.imageUri
- val imageInputStream = try {
- contentResolver.openInputStream(imageUri)!!
- } catch (e: FileNotFoundException){
- AlertDialog.Builder(this).apply {
- setMessage(getString(R.string.file_not_found).format(imageUri))
-
- setNegativeButton(android.R.string.ok) { _, _ -> }
- }.show()
- return
- }
-
- val imagePart = ProgressRequestBody(imageInputStream, data.size)
- val requestBody = MultipartBody.Builder()
- .setType(MultipartBody.FORM)
- .addFormDataPart("file", System.currentTimeMillis().toString(), imagePart)
- .build()
-
- val sub = imagePart.progressSubject
- .subscribeOn(Schedulers.io())
- .subscribe { percentage ->
- data.progress = percentage.toInt()
- binding.uploadProgressBar.progress =
- photoData.sumOf { it.progress ?: 0 } / photoData.size
- }
-
- var postSub: Disposable? = null
-
- val description = data.imageDescription?.let { MultipartBody.Part.createFormData("description", it) }
-
- val api = apiHolder.api ?: apiHolder.setToCurrentUser()
- val inter = api.mediaUpload(description, requestBody.parts[0])
-
- postSub = inter
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- { attachment: Attachment ->
- data.progress = 0
- data.uploadId = attachment.id!!
- },
- { e: Throwable ->
- binding.uploadError.visibility = View.VISIBLE
- if(e is HttpException){
- binding.uploadErrorTextExplanation.text =
- getString(R.string.upload_error, e.code())
- binding.uploadErrorTextExplanation.visibility= VISIBLE
- } else {
- binding.uploadErrorTextExplanation.visibility= View.GONE
- }
- e.printStackTrace()
- postSub?.dispose()
- sub.dispose()
- },
- {
- data.progress = 100
- if (photoData.all { it.progress == 100 && it.uploadId != null }) {
- binding.uploadProgressBar.visibility = View.GONE
- binding.uploadCompletedTextview.visibility = View.VISIBLE
- post()
- }
- postSub?.dispose()
- sub.dispose()
- }
- )
- }
- }
-
- private fun post() {
- val description = binding.newPostDescriptionInputField.text.toString()
- enableButton(false)
- lifecycleScope.launchWhenCreated {
- try {
- val api = apiHolder.api ?: apiHolder.setToCurrentUser()
-
- api.postStatus(
- statusText = description,
- media_ids = photoData.mapNotNull { it.uploadId }.toList()
- )
- Toast.makeText(applicationContext, getString(R.string.upload_post_success),
- Toast.LENGTH_SHORT).show()
- val intent = Intent(this@PostCreationActivity, MainActivity::class.java)
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
- startActivity(intent)
- } catch (exception: IOException) {
- Toast.makeText(applicationContext, getString(R.string.upload_post_error),
- Toast.LENGTH_SHORT).show()
- Log.e(TAG, exception.toString())
- enableButton(true)
- } catch (exception: HttpException) {
- Toast.makeText(applicationContext, getString(R.string.upload_post_failed),
- Toast.LENGTH_SHORT).show()
- Log.e(TAG, exception.response().toString() + exception.message().toString())
- enableButton(true)
- }
- }
- }
-
private fun enableButton(enable: Boolean = true){
binding.postCreationSendButton.isEnabled = enable
if(enable){
@@ -469,32 +309,8 @@ class PostCreationActivity : BaseActivity() {
result: ActivityResult? ->
if (result?.resultCode == Activity.RESULT_OK && result.data != null) {
val position: Int = result.data!!.getIntExtra(PhotoEditActivity.PICTURE_POSITION, 0)
- photoData.getOrNull(position)?.apply {
- if (video) {
- val muted: Boolean = result.data!!.getBooleanExtra(VideoEditActivity.MUTED, false)
- val videoStart: Float? = result.data!!.getFloatExtra(VideoEditActivity.VIDEO_START, -1f).let {
- if(it == -1f) null else it
- }
- val modified: Boolean = result.data!!.getBooleanExtra(VideoEditActivity.MODIFIED, false)
- val videoEnd: Float? = result.data!!.getFloatExtra(VideoEditActivity.VIDEO_END, -1f).let {
- if(it == -1f) null else it
- }
- if(modified){
- videoEncodeProgress = 0
- sessionMap[position]?.let { FFmpegKit.cancel(it) }
- startEncoding(position, muted, videoStart, videoEnd)
- }
- } else {
- imageUri = result.data!!.getStringExtra(PhotoEditActivity.PICTURE_URI)!!.toUri()
- val (imageSize, imageVideo) = imageUri.getSizeAndVideoValidate(position)
- size = imageSize
- video = imageVideo
- }
- progress = null
- uploadId = null
- } ?: Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
-
- binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription, it.video, it.videoEncodeProgress) })
+ model.modifyAt(position, result.data!!)
+ ?: Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
} else if(result?.resultCode != Activity.RESULT_CANCELED){
Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
}
@@ -508,21 +324,21 @@ class PostCreationActivity : BaseActivity() {
* don't want to remove the end
*/
private fun startEncoding(position: Int, muted: Boolean, videoStart: Float?, videoEnd: Float?) {
- val originalUri = photoData[position].imageUri
+ val originalUri = model.getPhotoData().value!![position].imageUri
// Having a meaningful suffix is necessary so that ffmpeg knows what to put in output
val suffix = if(originalUri.scheme == "content") {
- contentResolver.getType(photoData[position].imageUri)?.takeLastWhile { it != '/' }
+ contentResolver.getType(model.getPhotoData().value!![position].imageUri)?.takeLastWhile { it != '/' }
} else {
originalUri.toString().takeLastWhile { it != '/' }
}
val file = File.createTempFile("temp_video", ".$suffix")
//val file = File.createTempFile("temp_video", ".webm")
- tempFiles.add(file)
+ model.trackTempFile(file)
val fileUri = file.toUri()
val outputVideoPath = ffmpegSafeUri(fileUri)
- val inputUri = photoData[position].imageUri
+ val inputUri = model.getPhotoData().value!![position].imageUri
val inputSafePath = ffmpegSafeUri(inputUri)
@@ -542,18 +358,12 @@ class PostCreationActivity : BaseActivity() {
fun successResult() {
// Hide progress indicator in carousel
binding.carousel.updateProgress(null, position, false)
- val (imageSize, imageVideo) = outputVideoPath.toUri().let {
- photoData[position].imageUri = it
- it.getSizeAndVideoValidate(position)
+ val (imageSize, _) = outputVideoPath.toUri().let {
+ model.setUriAtPosition(it, position)
+ model.getSizeAndVideoValidate(it, position)
}
- photoData[position].videoEncodeProgress = null
- photoData[position].size = imageSize
- binding.carousel.addData(photoData.map {
- CarouselItem(it.imageUri,
- it.imageDescription,
- it.video,
- it.videoEncodeProgress)
- })
+ model.setVideoEncodeAtPosition(position, null)
+ model.setSizeAtPosition(imageSize, position)
}
val post = resultHandler.post {
@@ -567,7 +377,7 @@ class PostCreationActivity : BaseActivity() {
} else {
resultHandler.post {
binding.carousel.updateProgress(null, position, error = true)
- photoData[position].videoEncodeProgress = null
+ model.setVideoEncodeAtPosition(position, null)
}
Log.e(TAG, "Encode failed with state ${session.state} and rc $returnCode.${session.failStackTrace}")
}
@@ -584,8 +394,8 @@ class PostCreationActivity : BaseActivity() {
}
resultHandler.post {
completePercentage?.let {
- val rounded = it.roundToInt()
- photoData[position].videoEncodeProgress = rounded
+ val rounded: Int = it.roundToInt()
+ model.setVideoEncodeAtPosition(position, rounded)
binding.carousel.updateProgress(rounded, position, false)
}
}
@@ -593,15 +403,15 @@ class PostCreationActivity : BaseActivity() {
}
}
}
- sessionMap[position] = session.sessionId
+ model.registerNewFFmpegSession(position, session.sessionId)
}
private fun edit(position: Int) {
val intent = Intent(
this,
- if(photoData[position].video) VideoEditActivity::class.java else PhotoEditActivity::class.java
+ if(model.getPhotoData().value!![position].video) VideoEditActivity::class.java else PhotoEditActivity::class.java
)
- .putExtra(PhotoEditActivity.PICTURE_URI, photoData[position].imageUri)
+ .putExtra(PhotoEditActivity.PICTURE_URI, model.getPhotoData().value!![position].imageUri)
.putExtra(PhotoEditActivity.PICTURE_POSITION, position)
editResultContract.launch(intent)
diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt
index 8b7f1d1b..6f4ad5be 100644
--- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt
+++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt
@@ -1,30 +1,436 @@
package org.pixeldroid.app.postCreation
+import android.app.Application
import android.content.ClipData
-import android.os.Bundle
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
+import android.content.Intent
+import android.net.Uri
+import android.provider.OpenableColumns
+import android.text.Editable
+import android.util.Log
+import android.widget.Toast
+import androidx.core.net.toFile
+import androidx.core.net.toUri
+import androidx.lifecycle.*
+import com.arthenica.ffmpegkit.FFmpegKit
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.disposables.Disposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import okhttp3.MultipartBody
+import org.pixeldroid.app.MainActivity
+import org.pixeldroid.app.R
+import org.pixeldroid.app.postCreation.photoEdit.PhotoEditActivity
+import org.pixeldroid.app.postCreation.photoEdit.VideoEditActivity
+import org.pixeldroid.app.utils.PixelDroidApplication
+import org.pixeldroid.app.utils.api.objects.Attachment
+import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
+import org.pixeldroid.app.utils.di.PixelfedAPIHolder
+import retrofit2.HttpException
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.IOException
+import javax.inject.Inject
+import kotlin.math.ceil
-class PostCreationViewModel : ViewModel() {
- private val photoData: MutableLiveData> by lazy {
- MutableLiveData>().also {
- loadUsers()
+// Models the UI state for the PostCreationActivity
+data class PostCreationActivityUiState(
+ val userMessage: String? = null,
+
+ val addPhotoButtonEnabled: Boolean = true,
+ val editPhotoButtonEnabled: Boolean = true,
+ val removePhotoButtonEnabled: Boolean = true,
+ val postCreationSendButtonEnabled: Boolean = true,
+
+ val isCarousel: Boolean = true,
+
+ val newPostDescriptionText: String = "",
+
+ val uploadProgressBarVisible: Boolean = false,
+ val uploadProgress: Int = 0,
+ val uploadCompletedTextviewVisible: Boolean = false,
+ val uploadErrorVisible: Boolean = false,
+ val uploadErrorExplanationText: String = "",
+ val uploadErrorExplanationVisible: Boolean = false,
+
+ val newEncodingJobPosition: Int? = null,
+ val newEncodingJobMuted: Boolean? = null,
+ val newEncodingJobVideoStart: Float? = null,
+ val newEncodingJobVideoEnd: Float? = null,
+)
+
+class PostCreationViewModel(application: Application, clipdata: ClipData? = null, val instance: InstanceDatabaseEntity? = null) : AndroidViewModel(application) {
+ private val photoData: MutableLiveData> by lazy {
+ MutableLiveData>().also {
+ it.value = clipdata?.let { it1 -> addPossibleImages(it1, mutableListOf()) }
}
}
- fun getUsers(): LiveData> {
- return photoData
+ @Inject
+ lateinit var apiHolder: PixelfedAPIHolder
+
+ init {
+ (application as PixelDroidApplication).getAppComponent().inject(this)
}
- private fun loadUsers() {
- // Do an asynchronous operation to fetch users.
+ // Map photoData indexes to FFmpeg Session IDs
+ private val sessionMap: MutableMap = mutableMapOf()
+ // Keep track of temporary files to delete them (avoids filling cache super fast with videos)
+ private val tempFiles: java.util.ArrayList = java.util.ArrayList()
+
+
+ private val _uiState = MutableStateFlow(PostCreationActivityUiState())
+ val uiState: StateFlow = _uiState
+
+ fun userMessageShown() {
+ _uiState.update { currentUiState ->
+ currentUiState.copy(userMessage = null)
+ }
}
+
+ fun encodingStarted() {
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ newEncodingJobPosition = null,
+ newEncodingJobMuted = null,
+ newEncodingJobVideoStart = null,
+ newEncodingJobVideoEnd = null,
+ )
+ }
+ }
+
+ fun getPhotoData(): LiveData> = photoData
+
+ /**
+ * Will add as many images as possible to [photoData], from the [clipData], and if
+ * ([photoData].size + [clipData].itemCount) > [InstanceDatabaseEntity.albumLimit] then it will only add as many images
+ * as are legal (if any) and a dialog will be shown to the user alerting them of this fact.
+ */
+ fun addPossibleImages(clipData: ClipData, previousList: MutableList? = photoData.value): MutableList {
+ val dataToAdd: ArrayList = arrayListOf()
+ var count = clipData.itemCount
+ if(count + (previousList?.size ?: 0) > instance!!.albumLimit){
+ _uiState.update { currentUiState ->
+ currentUiState.copy(userMessage = getApplication().getString(R.string.total_exceeds_album_limit).format(instance.albumLimit))
+ }
+ count = count.coerceAtMost(instance.albumLimit - (previousList?.size ?: 0))
+ }
+ if (count + (previousList?.size ?: 0) >= instance.albumLimit) {
+ // Disable buttons to add more images
+ _uiState.update { currentUiState ->
+ currentUiState.copy(addPhotoButtonEnabled = false)
+ }
+ }
+ for (i in 0 until count) {
+ clipData.getItemAt(i).uri.let {
+ val sizeAndVideoPair: Pair =
+ getSizeAndVideoValidate(it, (previousList?.size ?: 0) + dataToAdd.size + 1)
+ dataToAdd.add(PhotoData(imageUri = it, size = sizeAndVideoPair.first, video = sizeAndVideoPair.second))
+ }
+ }
+ return previousList?.plus(dataToAdd)?.toMutableList() ?: mutableListOf()
+ }
+
+ fun setImages(addPossibleImages: MutableList) {
+ photoData.value = addPossibleImages
+ }
+
+ /**
+ * Returns the size of the file of the Uri, and whether it is a video,
+ * and opens a dialog in case it is too big or in case the file is unsupported.
+ */
+ fun getSizeAndVideoValidate(uri: Uri, editPosition: Int): Pair {
+ val size: Long =
+ if (uri.scheme =="content") {
+ getApplication().contentResolver.query(uri, null, null, null, null)
+ ?.use { cursor ->
+ /* Get the column indexes of the data in the Cursor,
+ * move to the first row in the Cursor, get the data,
+ * and display it.
+ */
+ val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
+ cursor.moveToFirst()
+ cursor.getLong(sizeIndex)
+ } ?: 0
+ } else {
+ uri.toFile().length()
+ }
+
+ val sizeInkBytes = ceil(size.toDouble() / 1000).toLong()
+ val type = getApplication().contentResolver.getType(uri)
+ val isVideo = type?.startsWith("video/") == true
+
+ if(isVideo && !instance!!.videoEnabled){
+ _uiState.update { currentUiState ->
+ currentUiState.copy(userMessage = getApplication().getString(R.string.video_not_supported))
+ }
+ }
+
+ if (sizeInkBytes > instance!!.maxPhotoSize || sizeInkBytes > instance.maxVideoSize) {
+ val maxSize = if (isVideo) instance.maxVideoSize else instance.maxPhotoSize
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ userMessage = getApplication().getString(R.string.size_exceeds_instance_limit, editPosition, sizeInkBytes, maxSize)
+ )
+ }
+ }
+ return Pair(size, isVideo)
+ }
+
+ fun isNotEmpty(): Boolean = photoData.value?.isNotEmpty() ?: false
+
+ fun updateDescription(position: Int, description: String) {
+ photoData.value?.getOrNull(position)?.imageDescription = description
+ photoData.value = photoData.value
+ }
+
+ fun resetUploadStatus() {
+ photoData.value = photoData.value?.map { it.copy(uploadId = null, progress = null) }?.toMutableList()
+ }
+
+ fun setVideoEncodeAtPosition(position: Int, progress: Int?) {
+ photoData.value?.set(position, photoData.value!![position].copy(videoEncodeProgress = progress))
+ photoData.value = photoData.value
+ }
+
+ fun setUriAtPosition(uri: Uri, position: Int) {
+ photoData.value?.set(position, photoData.value!![position].copy(imageUri = uri))
+ photoData.value = photoData.value
+ }
+
+ fun setSizeAtPosition(imageSize: Long, position: Int) {
+ photoData.value?.set(position, photoData.value!![position].copy(size = imageSize))
+ photoData.value = photoData.value
+ }
+
+ fun removeAt(currentPosition: Int) {
+ photoData.value?.removeAt(currentPosition)
+ _uiState.update {
+ it.copy(
+ addPhotoButtonEnabled = true
+ )
+ }
+ photoData.value = photoData.value
+ }
+
+ /**
+ * Uploads the images that are in the [photoData] array.
+ * Keeps track of them in the [PhotoData.progress] (for the upload progress), and the
+ * [PhotoData.uploadId] (for the list of ids of the uploads).
+ */
+ fun upload() {
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ postCreationSendButtonEnabled = false,
+ addPhotoButtonEnabled = false,
+ editPhotoButtonEnabled = false,
+ removePhotoButtonEnabled = false,
+ uploadCompletedTextviewVisible = false,
+ uploadErrorVisible = false,
+ uploadProgressBarVisible = true
+ )
+ }
+
+ for (data: PhotoData in getPhotoData().value ?: emptyList()) {
+ val imageUri = data.imageUri
+ val imageInputStream = try {
+ getApplication().contentResolver.openInputStream(imageUri)!!
+ } catch (e: FileNotFoundException){
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ userMessage = getApplication().getString(R.string.file_not_found,
+ imageUri)
+ )
+ }
+ return
+ }
+
+ val imagePart = ProgressRequestBody(imageInputStream, data.size)
+ val requestBody = MultipartBody.Builder()
+ .setType(MultipartBody.FORM)
+ .addFormDataPart("file", System.currentTimeMillis().toString(), imagePart)
+ .build()
+
+ val sub = imagePart.progressSubject
+ .subscribeOn(Schedulers.io())
+ .subscribe { percentage ->
+ data.progress = percentage.toInt()
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ uploadProgress = getPhotoData().value!!.sumOf { it.progress ?: 0 } / getPhotoData().value!!.size
+ )
+ }
+ }
+
+ var postSub: Disposable? = null
+
+ val description = data.imageDescription?.let { MultipartBody.Part.createFormData("description", it) }
+
+ val api = apiHolder.api ?: apiHolder.setToCurrentUser()
+ val inter = api.mediaUpload(description, requestBody.parts[0])
+
+ postSub = inter
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ { attachment: Attachment ->
+ data.progress = 0
+ data.uploadId = attachment.id!!
+ },
+ { e: Throwable ->
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ uploadErrorVisible = true,
+ uploadErrorExplanationText = if(e is HttpException){
+ getApplication().getString(R.string.upload_error, e.code())
+ } else "",
+ uploadErrorExplanationVisible = e is HttpException,
+ )
+ }
+ e.printStackTrace()
+ postSub?.dispose()
+ sub.dispose()
+ },
+ {
+ data.progress = 100
+ if (getPhotoData().value!!.all { it.progress == 100 && it.uploadId != null }) {
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ uploadProgressBarVisible = false,
+ uploadCompletedTextviewVisible = true
+ )
+ }
+ post()
+ }
+ postSub?.dispose()
+ sub.dispose()
+ }
+ )
+ }
+ }
+
+ private fun post() {
+ val description = uiState.value.newPostDescriptionText
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ postCreationSendButtonEnabled = false
+ )
+ }
+ viewModelScope.launch {
+ try {
+ val api = apiHolder.api ?: apiHolder.setToCurrentUser()
+
+ api.postStatus(
+ statusText = description,
+ media_ids = getPhotoData().value!!.mapNotNull { it.uploadId }.toList()
+ )
+ Toast.makeText(getApplication(), getApplication().getString(R.string.upload_post_success),
+ Toast.LENGTH_SHORT).show()
+ val intent = Intent(getApplication(), MainActivity::class.java)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ //TODO make the activity launch this instead (and surrounding toasts too)L
+ getApplication().startActivity(intent)
+ } catch (exception: IOException) {
+ Toast.makeText(getApplication(), getApplication().getString(R.string.upload_post_error),
+ Toast.LENGTH_SHORT).show()
+ Log.e(TAG, exception.toString())
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ postCreationSendButtonEnabled = true
+ )
+ }
+ } catch (exception: HttpException) {
+ Toast.makeText(getApplication(), getApplication().getString(R.string.upload_post_failed),
+ Toast.LENGTH_SHORT).show()
+ Log.e(TAG, exception.response().toString() + exception.message().toString())
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ postCreationSendButtonEnabled = true
+ )
+ }
+ }
+ }
+ }
+
+ fun modifyAt(position: Int, data: Intent): Unit? {
+ val result: PhotoData = photoData.value?.get(position)?.run {
+ if (video) {
+ val muted: Boolean = data.getBooleanExtra(VideoEditActivity.MUTED, false)
+ val videoStart: Float? = data.getFloatExtra(VideoEditActivity.VIDEO_START, -1f).let {
+ if(it == -1f) null else it
+ }
+ val modified: Boolean = data.getBooleanExtra(VideoEditActivity.MODIFIED, false)
+ val videoEnd: Float? = data.getFloatExtra(VideoEditActivity.VIDEO_END, -1f).let {
+ if(it == -1f) null else it
+ }
+ if(modified){
+ videoEncodeProgress = 0
+ sessionMap[position]?.let { FFmpegKit.cancel(it) }
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ newEncodingJobPosition = position,
+ newEncodingJobMuted = muted,
+ newEncodingJobVideoStart = videoStart,
+ newEncodingJobVideoEnd = videoEnd
+ )
+ }
+ }
+ } else {
+ imageUri = data.getStringExtra(PhotoEditActivity.PICTURE_URI)!!.toUri()
+ val (imageSize, imageVideo) = getSizeAndVideoValidate(imageUri, position)
+ size = imageSize
+ video = imageVideo
+ }
+ progress = null
+ uploadId = null
+ this
+ } ?: return null
+ result.let {
+ photoData.value?.set(position, it)
+ photoData.value = photoData.value
+ }
+ return Unit
+ }
+
+ fun newPostDescriptionChanged(text: Editable?) {
+ _uiState.update { it.copy(newPostDescriptionText = text.toString()) }
+ }
+
+ fun trackTempFile(file: File) {
+ tempFiles.add(file)
+ }
+
+ fun cancelEncode(currentPosition: Int) {
+ sessionMap[currentPosition]?.let { FFmpegKit.cancel(it) }
+ }
+
+ override fun onCleared() {
+ super.onCleared()
+ FFmpegKit.cancel()
+ tempFiles.forEach {
+ it.delete()
+ }
+
+ }
+
+ fun registerNewFFmpegSession(position: Int, sessionId: Long) {
+ sessionMap[position] = sessionId
+ }
+
+ fun becameCarousel(became: Boolean) {
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ isCarousel = became
+ )
+ }
+ }
+
}
-class PostCreationViewModelFactory(val bundle: ClipData? = null) : ViewModelProvider.Factory {
- override fun create(modelClass: Class): T {
- return modelClass.getConstructor(ClipData::class.java).newInstance(bundle)
- }
+class PostCreationViewModelFactory(val application: Application, val clipdata: ClipData, val instance: InstanceDatabaseEntity) : ViewModelProvider.Factory {
+ override fun create(modelClass: Class): T {
+ return modelClass.getConstructor(Application::class.java, ClipData::class.java, InstanceDatabaseEntity::class.java).newInstance(application, clipdata, instance)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/carousel/ImageCarousel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/carousel/ImageCarousel.kt
index bb2473aa..58712df5 100644
--- a/app/src/main/java/org/pixeldroid/app/postCreation/carousel/ImageCarousel.kt
+++ b/app/src/main/java/org/pixeldroid/app/postCreation/carousel/ImageCarousel.kt
@@ -59,15 +59,7 @@ class ImageCarousel(
initIndicator()
}
-
-
- private var btnPrevious: View? = null
- private var btnNext: View? = null
-
- private var btnGrid: View? = null
- private var btnCarousel: View? = null
-
-
+
private var isBuiltInIndicator = false
private var data: MutableList? = null
@@ -231,27 +223,24 @@ class ImageCarousel(
set(value) {
field = value
- btnGrid = binding.switchToGridButton
- btnCarousel = binding.switchToCarouselButton
-
- btnGrid?.setOnClickListener {
+ binding.switchToGridButton.setOnClickListener {
layoutCarousel = false
}
- btnCarousel?.setOnClickListener {
+ binding.switchToCarouselButton.setOnClickListener {
layoutCarousel = true
}
if(value){
if(layoutCarousel){
- btnGrid?.visibility = VISIBLE
- btnCarousel?.visibility = GONE
+ binding.switchToGridButton.visibility = VISIBLE
+ binding.switchToCarouselButton.visibility = GONE
} else {
- btnGrid?.visibility = GONE
- btnCarousel?.visibility = VISIBLE
+ binding.switchToGridButton.visibility = GONE
+ binding.switchToCarouselButton.visibility = VISIBLE
}
} else {
- btnGrid?.visibility = GONE
- btnCarousel?.visibility = GONE
+ binding.switchToGridButton.visibility = GONE
+ binding.switchToCarouselButton.visibility = GONE
}
}
@@ -267,15 +256,15 @@ class ImageCarousel(
if(value){
recyclerView.layoutManager = CarouselLinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
- btnNext?.visibility = VISIBLE
- btnPrevious?.visibility = VISIBLE
+ binding.btnNext.visibility = VISIBLE
+ binding.btnPrevious.visibility = VISIBLE
binding.editMediaDescriptionLayout.visibility = if(editingMediaDescription) VISIBLE else INVISIBLE
tvCaption.visibility = if(editingMediaDescription) INVISIBLE else VISIBLE
} else {
recyclerView.layoutManager = GridLayoutManager(context, 3)
- btnNext?.visibility = GONE
- btnPrevious?.visibility = GONE
+ binding.btnNext.visibility = GONE
+ binding.btnPrevious.visibility = GONE
binding.editMediaDescriptionLayout.visibility = INVISIBLE
tvCaption.visibility = INVISIBLE
diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt
index 11d0526a..22f92dc9 100644
--- a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt
+++ b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt
@@ -15,8 +15,6 @@ import android.view.MenuItem
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
-import android.widget.SeekBar
-import android.widget.SeekBar.OnSeekBarChangeListener
import androidx.core.net.toUri
import androidx.core.os.HandlerCompat
import androidx.media.AudioAttributesCompat
@@ -33,10 +31,6 @@ import org.pixeldroid.app.postCreation.carousel.dpToPx
import org.pixeldroid.app.utils.BaseActivity
import org.pixeldroid.app.utils.ffmpegSafeUri
import java.io.File
-import java.text.NumberFormat
-import java.time.format.DateTimeFormatter
-import java.util.*
-import kotlin.collections.ArrayList
class VideoEditActivity : BaseActivity() {
diff --git a/app/src/main/java/org/pixeldroid/app/utils/di/ApplicationComponent.kt b/app/src/main/java/org/pixeldroid/app/utils/di/ApplicationComponent.kt
index 3a59eec0..0a646b08 100644
--- a/app/src/main/java/org/pixeldroid/app/utils/di/ApplicationComponent.kt
+++ b/app/src/main/java/org/pixeldroid/app/utils/di/ApplicationComponent.kt
@@ -7,6 +7,7 @@ import org.pixeldroid.app.utils.PixelDroidApplication
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.BaseFragment
import dagger.Component
+import org.pixeldroid.app.postCreation.PostCreationViewModel
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker
import javax.inject.Singleton
@@ -18,6 +19,7 @@ interface ApplicationComponent {
fun inject(activity: BaseActivity?)
fun inject(feedFragment: BaseFragment)
fun inject(notificationsWorker: NotificationsWorker)
+ fun inject(postCreationViewModel: PostCreationViewModel)
val context: Context?
val application: Application?
From 3c7f46d75a5f9a2f4dacee89ec95e218244e81b2 Mon Sep 17 00:00:00 2001
From: Matthieu <24-artectrex@users.noreply.shinice.net>
Date: Sun, 19 Jun 2022 14:28:00 +0200
Subject: [PATCH 4/5] Fix bug where all saved files were pngs
---
.../app/postCreation/PostCreationActivity.kt | 42 ++++++++-----------
.../java/org/pixeldroid/app/utils/Utils.kt | 7 ++++
2 files changed, 25 insertions(+), 24 deletions(-)
diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt
index 63cd86af..2f54a5e0 100644
--- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt
+++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt
@@ -19,7 +19,6 @@ import androidx.activity.viewModels
import androidx.core.net.toFile
import androidx.core.net.toUri
import androidx.core.os.HandlerCompat
-import androidx.core.widget.addTextChangedListener
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
@@ -27,29 +26,20 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView
import com.arthenica.ffmpegkit.*
import com.google.android.material.snackbar.Snackbar
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
-import io.reactivex.rxjava3.disposables.Disposable
-import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
-import okhttp3.MultipartBody
-import org.pixeldroid.app.MainActivity
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityPostCreationBinding
import org.pixeldroid.app.postCreation.camera.CameraActivity
import org.pixeldroid.app.postCreation.carousel.CarouselItem
-import org.pixeldroid.app.postCreation.carousel.ImageCarousel
import org.pixeldroid.app.postCreation.photoEdit.PhotoEditActivity
import org.pixeldroid.app.postCreation.photoEdit.VideoEditActivity
import org.pixeldroid.app.utils.BaseActivity
-import org.pixeldroid.app.utils.api.objects.Attachment
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.ffmpegSafeUri
-import retrofit2.HttpException
+import org.pixeldroid.app.utils.fileExtension
import java.io.File
-import java.io.FileNotFoundException
-import java.io.IOException
import java.io.OutputStream
import java.text.SimpleDateFormat
import java.util.*
@@ -214,13 +204,13 @@ class PostCreationActivity : BaseActivity() {
}
private fun savePicture(button: View, currentPosition: Int) {
- val name = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US)
- .format(System.currentTimeMillis()) + ".png"
- val pair = getOutputFile(name)
+ val originalUri = model.getPhotoData().value!![currentPosition].imageUri
+
+ val pair = getOutputFile(originalUri)
val outputStream: OutputStream = pair.first
val path: String = pair.second
- contentResolver.openInputStream(model.getPhotoData().value!![currentPosition].imageUri)!!.use { input ->
+ contentResolver.openInputStream(originalUri)!!.use { input ->
outputStream.use { output ->
input.copyTo(output)
}
@@ -246,20 +236,28 @@ class PostCreationActivity : BaseActivity() {
).show()
}
- private fun getOutputFile(name: String): Pair {
+ private fun getOutputFile(uri: Uri): Pair {
+ val extension = uri.fileExtension(contentResolver)
+
+ val name = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US)
+ .format(System.currentTimeMillis()) + ".$extension"
+
val outputStream: OutputStream
val path: String
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver: ContentResolver = contentResolver
+ val type = resolver.getType(uri)
val contentValues = ContentValues()
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name)
- contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
+ contentValues.put(MediaStore.MediaColumns.MIME_TYPE, type)
contentValues.put(
MediaStore.MediaColumns.RELATIVE_PATH,
Environment.DIRECTORY_PICTURES
)
- val imageUri: Uri =
- resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)!!
+ val imageUri: Uri = resolver.insert(
+ if (type?.startsWith("image") == true) MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ else MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+ contentValues)!!
path = imageUri.toString()
outputStream = resolver.openOutputStream(Objects.requireNonNull(imageUri))!!
} else {
@@ -327,11 +325,7 @@ class PostCreationActivity : BaseActivity() {
val originalUri = model.getPhotoData().value!![position].imageUri
// Having a meaningful suffix is necessary so that ffmpeg knows what to put in output
- val suffix = if(originalUri.scheme == "content") {
- contentResolver.getType(model.getPhotoData().value!![position].imageUri)?.takeLastWhile { it != '/' }
- } else {
- originalUri.toString().takeLastWhile { it != '/' }
- }
+ val suffix = originalUri.fileExtension(contentResolver)
val file = File.createTempFile("temp_video", ".$suffix")
//val file = File.createTempFile("temp_video", ".webm")
model.trackTempFile(file)
diff --git a/app/src/main/java/org/pixeldroid/app/utils/Utils.kt b/app/src/main/java/org/pixeldroid/app/utils/Utils.kt
index b2ef5187..e0335d88 100644
--- a/app/src/main/java/org/pixeldroid/app/utils/Utils.kt
+++ b/app/src/main/java/org/pixeldroid/app/utils/Utils.kt
@@ -45,6 +45,13 @@ fun validDomain(domain: String?): Boolean {
return true
}
+fun Uri.fileExtension(contentResolver: ContentResolver): String? {
+ return if (scheme == "content") {
+ contentResolver.getType(this)?.takeLastWhile { it != '/' }
+ } else {
+ toString().takeLastWhile { it != '/' }
+ }
+}
fun Context.displayDimensionsInPx(): Pair {
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
From 8201bb6bae131fd3ba502c5faa4c0d5eee633d8d Mon Sep 17 00:00:00 2001
From: Matthieu <24-artectrex@users.noreply.shinice.net>
Date: Sun, 19 Jun 2022 16:29:59 +0200
Subject: [PATCH 5/5] Update licenses and switch to ffmpeg-kit-min
---
app/build.gradle | 2 +-
app/licenses.yml | 30 ++++++++++++++++++++++++------
app/src/main/assets/licenses.json | 2 +-
3 files changed, 26 insertions(+), 8 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index e09ea30c..84977ceb 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -154,7 +154,7 @@ dependencies {
*/
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
- implementation 'com.arthenica:ffmpeg-kit-full:4.5.1-1.LTS'
+ implementation 'com.arthenica:ffmpeg-kit-min:4.5.1-1.LTS'
implementation 'com.google.android.material:material:1.6.1'
diff --git a/app/licenses.yml b/app/licenses.yml
index 39aafb07..de647390 100644
--- a/app/licenses.yml
+++ b/app/licenses.yml
@@ -526,12 +526,6 @@
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
-- artifact: androidx.lifecycle:lifecycle-extensions:+
- name: lifecycle-extensions
- copyrightHolder: Google Inc.
- license: The Apache Software License, Version 2.0
- licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.lifecycle:lifecycle-process:+
name: lifecycle-process
copyrightHolder: Google Inc.
@@ -955,3 +949,27 @@
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/media2
+- artifact: com.davemorrissey.labs:subsampling-scale-image-view-androidx:+
+ name: subsampling-scale-image-view-androidx
+ copyrightHolder: David Morrissey and contributors
+ license: The Apache Software License, Version 2.0
+ licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
+ url: https://github.com/davemorrissey/subsampling-scale-image-view
+- artifact: com.arthenica:ffmpeg-kit-min:+
+ name: ffmpeg-kit-min
+ copyrightHolder: Taner Şener
+ license: GNU Lesser General Public License, Version 3
+ licenseUrl: https://www.gnu.org/licenses/lgpl-3.0.txt
+ url: https://github.com/tanersener/ffmpeg-kit
+- artifact: com.arthenica:smart-exception-java:+
+ name: smart-exception-java
+ copyrightHolder: Taner Şener
+ license: The 3-Clause BSD License
+ licenseUrl: https://opensource.org/licenses/BSD-3-Clause
+ url: https://github.com/tanersener/smart-exception
+- artifact: com.arthenica:smart-exception-common:+
+ name: smart-exception-common
+ copyrightHolder: Taner Şener
+ license: The 3-Clause BSD License
+ licenseUrl: https://opensource.org/licenses/BSD-3-Clause
+ url: https://github.com/tanersener/smart-exception
diff --git a/app/src/main/assets/licenses.json b/app/src/main/assets/licenses.json
index 3d00366e..93b50f51 100644
--- a/app/src/main/assets/licenses.json
+++ b/app/src/main/assets/licenses.json
@@ -1 +1 @@
-{"libraries":[{"artifactId":{"name":"materialdrawer-nav","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/MaterialDrawer","libraryName":"materialdrawer-nav"},{"artifactId":{"name":"materialdrawer-iconics","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/MaterialDrawer","libraryName":"materialdrawer-iconics"},{"artifactId":{"name":"materialdrawer","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/MaterialDrawer","libraryName":"materialdrawer"},{"artifactId":{"name":"startup-runtime","group":"androidx.startup","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/startup#1.0.0","libraryName":"startup-runtime"},{"artifactId":{"name":"iconics-views","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/Android-Iconics","libraryName":"iconics-views"},{"artifactId":{"name":"iconics-core","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/Android-Iconics","libraryName":"iconics-core"},{"artifactId":{"name":"iconics-typeface-api","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/Android-Iconics","libraryName":"iconics-typeface-api"},{"artifactId":{"name":"kotlin-stdlib-jdk8","group":"org.jetbrains.kotlin","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://kotlinlang.org/","libraryName":"kotlin-stdlib-jdk8"},{"artifactId":{"name":"kotlin-stdlib-jdk7","group":"org.jetbrains.kotlin","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://kotlinlang.org/","libraryName":"kotlin-stdlib-jdk7"},{"artifactId":{"name":"preference","group":"androidx.preference","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"preference"},{"artifactId":{"name":"constraintlayout","group":"androidx.constraintlayout","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://tools.android.com","libraryName":"constraintlayout"},{"artifactId":{"name":"camera-view","group":"androidx.camera","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/camera#1.0.0-alpha15","libraryName":"camera-view"},{"artifactId":{"name":"navigation-ui-ktx","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-ui-ktx"},{"artifactId":{"name":"navigation-ui","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-ui"},{"artifactId":{"name":"dexter","group":"com.karumi","version":"+"},"copyrightHolder":"Karumi and contributors","copyrightStatement":"Copyright © Karumi and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/karumi/Dexter","libraryName":"dexter"},{"artifactId":{"name":"material","group":"com.google.android.material","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/material-components/material-components-android","libraryName":"material"},{"artifactId":{"name":"dagger-android-support","group":"com.google.dagger","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Apache 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/google/dagger","libraryName":"dagger-android-support"},{"artifactId":{"name":"imagefilters","group":"info.androidhive","version":"+"},"copyrightHolder":"Zomato and ravi8x","copyrightStatement":"Copyright © Zomato and ravi8x. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","libraryName":"imagefilters"},{"artifactId":{"name":"ucrop","group":"com.github.yalantis","version":"+"},"copyrightHolder":"Yalantis","copyrightStatement":"Copyright © Yalantis. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0","normalizedLicense":"apache2","url":"https://github.com/Yalantis/uCrop","libraryName":"ucrop"},{"artifactId":{"name":"tracedroid","group":"com.github.ligi","version":"+"},"copyrightHolder":"ligi","copyrightStatement":"Copyright © ligi. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","url":"https://github.com/ligi/tracedroid","libraryName":"tracedroid"},{"artifactId":{"name":"supportemail","group":"com.github.ligi.tracedroid","version":"+"},"copyrightHolder":"ligi","copyrightStatement":"Copyright © ligi. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","url":"https://github.com/ligi/tracedroid","libraryName":"supportemail"},{"artifactId":{"name":"lib","group":"com.github.ligi.tracedroid","version":"+"},"copyrightHolder":"ligi","copyrightStatement":"Copyright © ligi. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","url":"https://github.com/ligi/tracedroid","libraryName":"lib"},{"artifactId":{"name":"timber","group":"com.jakewharton.timber","version":"+"},"copyrightHolder":"Jake Wharton and contributors","copyrightStatement":"Copyright © Jake Wharton and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/JakeWharton/timber","libraryName":"timber"},{"artifactId":{"name":"fastadapter-extensions-expandable","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/FastAdapter","libraryName":"fastadapter-extensions-expandable"},{"artifactId":{"name":"fastadapter","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/FastAdapter","libraryName":"fastadapter"},{"artifactId":{"name":"appcompat","group":"androidx.appcompat","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"appcompat"},{"artifactId":{"name":"navigation-fragment-ktx","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-fragment-ktx"},{"artifactId":{"name":"fragment-ktx","group":"androidx.fragment","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"fragment-ktx"},{"artifactId":{"name":"navigation-runtime-ktx","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-runtime-ktx"},{"artifactId":{"name":"navigation-common-ktx","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-common-ktx"},{"artifactId":{"name":"activity-ktx","group":"androidx.activity","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"activity-ktx"},{"artifactId":{"name":"core-ktx","group":"androidx.core","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"core-ktx"},{"artifactId":{"name":"navigation-fragment","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-fragment"},{"artifactId":{"name":"browser","group":"androidx.browser","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"browser"},{"artifactId":{"name":"paging-runtime-ktx","group":"androidx.paging","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"paging-runtime-ktx"},{"artifactId":{"name":"paging-runtime","group":"androidx.paging","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"paging-runtime"},{"artifactId":{"name":"viewpager2","group":"androidx.viewpager2","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"viewpager2"},{"artifactId":{"name":"recyclerview","group":"androidx.recyclerview","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"recyclerview"},{"artifactId":{"name":"legacy-support-v4","group":"androidx.legacy","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"legacy-support-v4"},{"artifactId":{"name":"legacy-support-core-ui","group":"androidx.legacy","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"legacy-support-core-ui"},{"artifactId":{"name":"swiperefreshlayout","group":"androidx.swiperefreshlayout","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"swiperefreshlayout"},{"artifactId":{"name":"lifecycle-livedata-ktx","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"lifecycle-livedata-ktx"},{"artifactId":{"name":"okhttp-integration","group":"com.github.bumptech.glide","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"okhttp-integration"},{"artifactId":{"name":"glide","group":"com.github.bumptech.glide","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"glide"},{"artifactId":{"name":"fragment","group":"androidx.fragment","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"fragment"},{"artifactId":{"name":"navigation-runtime","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-runtime"},{"artifactId":{"name":"activity","group":"androidx.activity","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"activity"},{"artifactId":{"name":"lifecycle-viewmodel-savedstate","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-viewmodel-savedstate"},{"artifactId":{"name":"lifecycle-runtime-ktx","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"lifecycle-runtime-ktx"},{"artifactId":{"name":"camera-camera2","group":"androidx.camera","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/camera#1.0.0-beta08","libraryName":"camera-camera2"},{"artifactId":{"name":"camera-lifecycle","group":"androidx.camera","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/camera#1.0.0-beta08","libraryName":"camera-lifecycle"},{"artifactId":{"name":"camera-core","group":"androidx.camera","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/camera#1.0.0-beta08","libraryName":"camera-core"},{"artifactId":{"name":"dagger-android","group":"com.google.dagger","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Apache 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/google/dagger","libraryName":"dagger-android"},{"artifactId":{"name":"cursoradapter","group":"androidx.cursoradapter","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"cursoradapter"},{"artifactId":{"name":"appcompat-resources","group":"androidx.appcompat","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"appcompat-resources"},{"artifactId":{"name":"drawerlayout","group":"androidx.drawerlayout","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"drawerlayout"},{"artifactId":{"name":"coordinatorlayout","group":"androidx.coordinatorlayout","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"coordinatorlayout"},{"artifactId":{"name":"viewpager","group":"androidx.viewpager","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"viewpager"},{"artifactId":{"name":"slidingpanelayout","group":"androidx.slidingpanelayout","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"slidingpanelayout"},{"artifactId":{"name":"customview","group":"androidx.customview","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"customview"},{"artifactId":{"name":"transition","group":"androidx.transition","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"transition"},{"artifactId":{"name":"vectordrawable-animated","group":"androidx.vectordrawable","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"vectordrawable-animated"},{"artifactId":{"name":"vectordrawable","group":"androidx.vectordrawable","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"vectordrawable"},{"artifactId":{"name":"media","group":"androidx.media","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"media"},{"artifactId":{"name":"legacy-support-core-utils","group":"androidx.legacy","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"legacy-support-core-utils"},{"artifactId":{"name":"loader","group":"androidx.loader","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"loader"},{"artifactId":{"name":"navigation-common","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-common"},{"artifactId":{"name":"asynclayoutinflater","group":"androidx.asynclayoutinflater","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"asynclayoutinflater"},{"artifactId":{"name":"core","group":"androidx.core","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"core"},{"artifactId":{"name":"versionedparcelable","group":"androidx.versionedparcelable","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"versionedparcelable"},{"artifactId":{"name":"collection-ktx","group":"androidx.collection","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"collection-ktx"},{"artifactId":{"name":"collection","group":"androidx.collection","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"collection"},{"artifactId":{"name":"concurrent-futures","group":"androidx.concurrent","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"concurrent-futures"},{"artifactId":{"name":"interpolator","group":"androidx.interpolator","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"interpolator"},{"artifactId":{"name":"savedstate","group":"androidx.savedstate","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"savedstate"},{"artifactId":{"name":"lifecycle-viewmodel-ktx","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"lifecycle-viewmodel-ktx"},{"artifactId":{"name":"lifecycle-viewmodel","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-viewmodel"},{"artifactId":{"name":"lifecycle-runtime","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-runtime"},{"artifactId":{"name":"room-ktx","group":"androidx.room","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"room-ktx"},{"artifactId":{"name":"room-runtime","group":"androidx.room","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"room-runtime"},{"artifactId":{"name":"room-common","group":"androidx.room","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"room-common"},{"artifactId":{"name":"sqlite-framework","group":"androidx.sqlite","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"sqlite-framework"},{"artifactId":{"name":"sqlite","group":"androidx.sqlite","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"sqlite"},{"artifactId":{"name":"cardview","group":"androidx.cardview","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"cardview"},{"artifactId":{"name":"exifinterface","group":"androidx.exifinterface","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"exifinterface"},{"artifactId":{"name":"gifdecoder","group":"com.github.bumptech.glide","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"gifdecoder"},{"artifactId":{"name":"paging-common-ktx","group":"androidx.paging","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"paging-common-ktx"},{"artifactId":{"name":"constraintlayout-core","group":"androidx.constraintlayout","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://tools.android.com","libraryName":"constraintlayout-core"},{"artifactId":{"name":"databinding-ktx","group":"androidx.databinding","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","libraryName":"databinding-ktx"},{"artifactId":{"name":"lifecycle-extensions","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-extensions"},{"artifactId":{"name":"lifecycle-process","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-process"},{"artifactId":{"name":"lifecycle-service","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-service"},{"artifactId":{"name":"work-runtime-ktx","group":"androidx.work","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/work#2.5.0","libraryName":"work-runtime-ktx"},{"artifactId":{"name":"work-runtime","group":"androidx.work","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/work#2.5.0","libraryName":"work-runtime"},{"artifactId":{"name":"paging-common","group":"androidx.paging","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"paging-common"},{"artifactId":{"name":"lifecycle-livedata","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-livedata"},{"artifactId":{"name":"lifecycle-livedata-core-ktx","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"lifecycle-livedata-core-ktx"},{"artifactId":{"name":"lifecycle-livedata-core","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-livedata-core"},{"artifactId":{"name":"core-runtime","group":"androidx.arch.core","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"core-runtime"},{"artifactId":{"name":"core-common","group":"androidx.arch.core","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"core-common"},{"artifactId":{"name":"lifecycle-common","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-common"},{"artifactId":{"name":"documentfile","group":"androidx.documentfile","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"documentfile"},{"artifactId":{"name":"localbroadcastmanager","group":"androidx.localbroadcastmanager","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"localbroadcastmanager"},{"artifactId":{"name":"print","group":"androidx.print","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"print"},{"artifactId":{"name":"annotation","group":"androidx.annotation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"annotation"},{"artifactId":{"name":"converter-gson","group":"com.squareup.retrofit2","version":"+"},"copyrightHolder":"Square, Inc.","copyrightStatement":"Copyright © Square, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/square/retrofit","libraryName":"converter-gson"},{"artifactId":{"name":"adapter-rxjava3","group":"com.squareup.retrofit2","version":"+"},"copyrightHolder":"Square, Inc.","copyrightStatement":"Copyright © Square, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/square/retrofit","libraryName":"adapter-rxjava3"},{"artifactId":{"name":"retrofit","group":"com.squareup.retrofit2","version":"+"},"copyrightHolder":"Square, Inc.","copyrightStatement":"Copyright © Square, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/square/retrofit","libraryName":"retrofit"},{"artifactId":{"name":"okhttp","group":"com.squareup.okhttp3","version":"+"},"copyrightHolder":"Square, Inc.","copyrightStatement":"Copyright © Square, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://square.github.io/okhttp/","libraryName":"okhttp"},{"artifactId":{"name":"rxandroid","group":"io.reactivex.rxjava3","version":"+"},"copyrightHolder":"Netflix, Inc","copyrightStatement":"Copyright © Netflix, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/ReactiveX/RxAndroid","libraryName":"rxandroid"},{"artifactId":{"name":"rxjava","group":"io.reactivex.rxjava3","version":"+"},"copyrightHolder":"Netflix, Inc.","copyrightStatement":"Copyright © Netflix, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/ReactiveX/RxJava","libraryName":"rxjava"},{"artifactId":{"name":"sparkbutton","group":"com.github.connyduck","version":"+"},"copyrightHolder":"varunest and ConnyDuck","copyrightStatement":"Copyright © varunest and ConnyDuck. All rights reserved.","license":"The Apache License, Version 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/connyduck/sparkbutton","libraryName":"sparkbutton"},{"artifactId":{"name":"recyclerview-integration","group":"com.github.bumptech.glide","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"recyclerview-integration"},{"artifactId":{"name":"google-material-typeface","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/Android-Iconics","libraryName":"google-material-typeface"},{"artifactId":{"name":"kotlinx-coroutines-android","group":"org.jetbrains.kotlinx","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/Kotlin/kotlinx.coroutines","libraryName":"kotlinx-coroutines-android"},{"artifactId":{"name":"viewbinding","group":"androidx.databinding","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","libraryName":"viewbinding"},{"artifactId":{"name":"kotlinx-coroutines-core-jvm","group":"org.jetbrains.kotlinx","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/Kotlin/kotlinx.coroutines","libraryName":"kotlinx-coroutines-core-jvm"},{"artifactId":{"name":"okhttp","group":"com.squareup.okhttp","version":"+"},"copyrightHolder":"Square, Inc.","copyrightStatement":"Copyright © Square, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","libraryName":"okhttp"},{"artifactId":{"name":"okio","group":"com.squareup.okio","version":"+"},"copyrightHolder":"Square, Inc.","copyrightStatement":"Copyright © Square, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/square/okio/","libraryName":"okio"},{"artifactId":{"name":"kotlin-stdlib","group":"org.jetbrains.kotlin","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://kotlinlang.org/","libraryName":"kotlin-stdlib"},{"artifactId":{"name":"listenablefuture","group":"com.google.guava","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","libraryName":"listenablefuture"},{"artifactId":{"name":"annotation-experimental","group":"androidx.annotation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"annotation-experimental"},{"artifactId":{"name":"auto-value-annotations","group":"com.google.auto.value","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","libraryName":"auto-value-annotations"},{"artifactId":{"name":"dagger","group":"com.google.dagger","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Apache 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/google/dagger","libraryName":"dagger"},{"artifactId":{"name":"dagger-lint-aar","group":"com.google.dagger","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Apache 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/google/dagger","libraryName":"dagger-lint-aar"},{"artifactId":{"name":"javax.inject","group":"javax.inject","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://code.google.com/p/atinject/","libraryName":"javax.inject"},{"artifactId":{"name":"gson","group":"com.google.code.gson","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","libraryName":"gson"},{"artifactId":{"name":"disklrucache","group":"com.github.bumptech.glide","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"disklrucache"},{"artifactId":{"name":"annotations","group":"com.github.bumptech.glide","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"annotations"},{"artifactId":{"name":"kotlin-stdlib-common","group":"org.jetbrains.kotlin","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://kotlinlang.org/","libraryName":"kotlin-stdlib-common"},{"artifactId":{"name":"annotations","group":"org.jetbrains","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://www.jetbrains.org","libraryName":"annotations"},{"artifactId":{"name":"gridlayout","group":"androidx.gridlayout","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"gridlayout"},{"artifactId":{"name":"preference-ktx","group":"androidx.preference","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"preference-ktx"},{"artifactId":{"name":"databinding-adapters","group":"androidx.databinding","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","libraryName":"databinding-adapters"},{"artifactId":{"name":"databinding-runtime","group":"androidx.databinding","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","libraryName":"databinding-runtime"},{"artifactId":{"name":"databinding-common","group":"androidx.databinding","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/studio","libraryName":"databinding-common"},{"artifactId":{"name":"lifecycle-common-java8","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-common-java8"},{"artifactId":{"name":"circleindicator","group":"me.relex","version":"+"},"copyrightHolder":"relex and contributors","copyrightStatement":"Copyright © relex and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/ongakuer/CircleIndicator","libraryName":"circleindicator"},{"artifactId":{"name":"dynamicanimation","group":"androidx.dynamicanimation","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"dynamicanimation"},{"artifactId":{"name":"savedstate-ktx","group":"androidx.savedstate","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/savedstate#1.1.0","libraryName":"savedstate-ktx"},{"artifactId":{"name":"tracing","group":"androidx.tracing","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/tracing#1.0.0","libraryName":"tracing"},{"artifactId":{"name":"emoji2-views-helper","group":"androidx.emoji2","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/emoji2#1.0.0","libraryName":"emoji2-views-helper"},{"artifactId":{"name":"emoji2","group":"androidx.emoji2","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/emoji2#1.0.0","libraryName":"emoji2"},{"artifactId":{"name":"resourceinspection-annotation","group":"androidx.resourceinspection","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/resourceinspection#1.0.0","libraryName":"resourceinspection-annotation"},{"artifactId":{"name":"room-paging","group":"androidx.room","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/room#2.4.1","libraryName":"room-paging"},{"artifactId":{"name":"window","group":"androidx.window","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/window#1.0.0","libraryName":"window"},{"artifactId":{"name":"animated-vector-drawable","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"animated-vector-drawable"},{"artifactId":{"name":"appcompat-v7","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"appcompat-v7"},{"artifactId":{"name":"support-annotations","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-annotations"},{"artifactId":{"name":"support-compat","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-compat"},{"artifactId":{"name":"support-core-ui","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-core-ui"},{"artifactId":{"name":"support-core-utils","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-core-utils"},{"artifactId":{"name":"support-fragment","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-fragment"},{"artifactId":{"name":"support-media-compat","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-media-compat"},{"artifactId":{"name":"support-v4","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-v4"},{"artifactId":{"name":"support-vector-drawable","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-vector-drawable"}]}
\ No newline at end of file
+{"libraries":[{"artifactId":{"name":"materialdrawer-nav","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/MaterialDrawer","libraryName":"materialdrawer-nav"},{"artifactId":{"name":"materialdrawer-iconics","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/MaterialDrawer","libraryName":"materialdrawer-iconics"},{"artifactId":{"name":"materialdrawer","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/MaterialDrawer","libraryName":"materialdrawer"},{"artifactId":{"name":"startup-runtime","group":"androidx.startup","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/startup#1.0.0","libraryName":"startup-runtime"},{"artifactId":{"name":"iconics-views","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/Android-Iconics","libraryName":"iconics-views"},{"artifactId":{"name":"iconics-core","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/Android-Iconics","libraryName":"iconics-core"},{"artifactId":{"name":"iconics-typeface-api","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/Android-Iconics","libraryName":"iconics-typeface-api"},{"artifactId":{"name":"kotlin-stdlib-jdk8","group":"org.jetbrains.kotlin","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://kotlinlang.org/","libraryName":"kotlin-stdlib-jdk8"},{"artifactId":{"name":"kotlin-stdlib-jdk7","group":"org.jetbrains.kotlin","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://kotlinlang.org/","libraryName":"kotlin-stdlib-jdk7"},{"artifactId":{"name":"preference","group":"androidx.preference","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"preference"},{"artifactId":{"name":"constraintlayout","group":"androidx.constraintlayout","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://tools.android.com","libraryName":"constraintlayout"},{"artifactId":{"name":"camera-view","group":"androidx.camera","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/camera#1.0.0-alpha15","libraryName":"camera-view"},{"artifactId":{"name":"navigation-ui-ktx","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-ui-ktx"},{"artifactId":{"name":"navigation-ui","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-ui"},{"artifactId":{"name":"dexter","group":"com.karumi","version":"+"},"copyrightHolder":"Karumi and contributors","copyrightStatement":"Copyright © Karumi and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/karumi/Dexter","libraryName":"dexter"},{"artifactId":{"name":"material","group":"com.google.android.material","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/material-components/material-components-android","libraryName":"material"},{"artifactId":{"name":"dagger-android-support","group":"com.google.dagger","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Apache 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/google/dagger","libraryName":"dagger-android-support"},{"artifactId":{"name":"imagefilters","group":"info.androidhive","version":"+"},"copyrightHolder":"Zomato and ravi8x","copyrightStatement":"Copyright © Zomato and ravi8x. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","libraryName":"imagefilters"},{"artifactId":{"name":"ucrop","group":"com.github.yalantis","version":"+"},"copyrightHolder":"Yalantis","copyrightStatement":"Copyright © Yalantis. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0","normalizedLicense":"apache2","url":"https://github.com/Yalantis/uCrop","libraryName":"ucrop"},{"artifactId":{"name":"tracedroid","group":"com.github.ligi","version":"+"},"copyrightHolder":"ligi","copyrightStatement":"Copyright © ligi. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","url":"https://github.com/ligi/tracedroid","libraryName":"tracedroid"},{"artifactId":{"name":"supportemail","group":"com.github.ligi.tracedroid","version":"+"},"copyrightHolder":"ligi","copyrightStatement":"Copyright © ligi. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","url":"https://github.com/ligi/tracedroid","libraryName":"supportemail"},{"artifactId":{"name":"lib","group":"com.github.ligi.tracedroid","version":"+"},"copyrightHolder":"ligi","copyrightStatement":"Copyright © ligi. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","url":"https://github.com/ligi/tracedroid","libraryName":"lib"},{"artifactId":{"name":"timber","group":"com.jakewharton.timber","version":"+"},"copyrightHolder":"Jake Wharton and contributors","copyrightStatement":"Copyright © Jake Wharton and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/JakeWharton/timber","libraryName":"timber"},{"artifactId":{"name":"fastadapter-extensions-expandable","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/FastAdapter","libraryName":"fastadapter-extensions-expandable"},{"artifactId":{"name":"fastadapter","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/FastAdapter","libraryName":"fastadapter"},{"artifactId":{"name":"appcompat","group":"androidx.appcompat","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"appcompat"},{"artifactId":{"name":"navigation-fragment-ktx","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-fragment-ktx"},{"artifactId":{"name":"fragment-ktx","group":"androidx.fragment","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"fragment-ktx"},{"artifactId":{"name":"navigation-runtime-ktx","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-runtime-ktx"},{"artifactId":{"name":"navigation-common-ktx","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-common-ktx"},{"artifactId":{"name":"activity-ktx","group":"androidx.activity","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"activity-ktx"},{"artifactId":{"name":"core-ktx","group":"androidx.core","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"core-ktx"},{"artifactId":{"name":"navigation-fragment","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-fragment"},{"artifactId":{"name":"browser","group":"androidx.browser","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"browser"},{"artifactId":{"name":"paging-runtime-ktx","group":"androidx.paging","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"paging-runtime-ktx"},{"artifactId":{"name":"paging-runtime","group":"androidx.paging","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"paging-runtime"},{"artifactId":{"name":"viewpager2","group":"androidx.viewpager2","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"viewpager2"},{"artifactId":{"name":"recyclerview","group":"androidx.recyclerview","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"recyclerview"},{"artifactId":{"name":"legacy-support-v4","group":"androidx.legacy","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"legacy-support-v4"},{"artifactId":{"name":"legacy-support-core-ui","group":"androidx.legacy","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"legacy-support-core-ui"},{"artifactId":{"name":"swiperefreshlayout","group":"androidx.swiperefreshlayout","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"swiperefreshlayout"},{"artifactId":{"name":"lifecycle-livedata-ktx","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"lifecycle-livedata-ktx"},{"artifactId":{"name":"okhttp-integration","group":"com.github.bumptech.glide","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"okhttp-integration"},{"artifactId":{"name":"glide","group":"com.github.bumptech.glide","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"glide"},{"artifactId":{"name":"fragment","group":"androidx.fragment","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"fragment"},{"artifactId":{"name":"navigation-runtime","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-runtime"},{"artifactId":{"name":"activity","group":"androidx.activity","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"activity"},{"artifactId":{"name":"lifecycle-viewmodel-savedstate","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-viewmodel-savedstate"},{"artifactId":{"name":"lifecycle-runtime-ktx","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"lifecycle-runtime-ktx"},{"artifactId":{"name":"camera-camera2","group":"androidx.camera","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/camera#1.0.0-beta08","libraryName":"camera-camera2"},{"artifactId":{"name":"camera-lifecycle","group":"androidx.camera","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/camera#1.0.0-beta08","libraryName":"camera-lifecycle"},{"artifactId":{"name":"camera-core","group":"androidx.camera","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/camera#1.0.0-beta08","libraryName":"camera-core"},{"artifactId":{"name":"dagger-android","group":"com.google.dagger","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Apache 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/google/dagger","libraryName":"dagger-android"},{"artifactId":{"name":"cursoradapter","group":"androidx.cursoradapter","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"cursoradapter"},{"artifactId":{"name":"appcompat-resources","group":"androidx.appcompat","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"appcompat-resources"},{"artifactId":{"name":"drawerlayout","group":"androidx.drawerlayout","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"drawerlayout"},{"artifactId":{"name":"coordinatorlayout","group":"androidx.coordinatorlayout","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"coordinatorlayout"},{"artifactId":{"name":"viewpager","group":"androidx.viewpager","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"viewpager"},{"artifactId":{"name":"slidingpanelayout","group":"androidx.slidingpanelayout","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"slidingpanelayout"},{"artifactId":{"name":"customview","group":"androidx.customview","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"customview"},{"artifactId":{"name":"transition","group":"androidx.transition","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"transition"},{"artifactId":{"name":"vectordrawable-animated","group":"androidx.vectordrawable","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"vectordrawable-animated"},{"artifactId":{"name":"vectordrawable","group":"androidx.vectordrawable","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"vectordrawable"},{"artifactId":{"name":"media","group":"androidx.media","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"media"},{"artifactId":{"name":"legacy-support-core-utils","group":"androidx.legacy","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"legacy-support-core-utils"},{"artifactId":{"name":"loader","group":"androidx.loader","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"loader"},{"artifactId":{"name":"navigation-common","group":"androidx.navigation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"navigation-common"},{"artifactId":{"name":"asynclayoutinflater","group":"androidx.asynclayoutinflater","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"asynclayoutinflater"},{"artifactId":{"name":"core","group":"androidx.core","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"core"},{"artifactId":{"name":"versionedparcelable","group":"androidx.versionedparcelable","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"versionedparcelable"},{"artifactId":{"name":"collection-ktx","group":"androidx.collection","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"collection-ktx"},{"artifactId":{"name":"collection","group":"androidx.collection","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"collection"},{"artifactId":{"name":"concurrent-futures","group":"androidx.concurrent","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"concurrent-futures"},{"artifactId":{"name":"interpolator","group":"androidx.interpolator","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"interpolator"},{"artifactId":{"name":"savedstate","group":"androidx.savedstate","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"savedstate"},{"artifactId":{"name":"lifecycle-viewmodel-ktx","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"lifecycle-viewmodel-ktx"},{"artifactId":{"name":"lifecycle-viewmodel","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-viewmodel"},{"artifactId":{"name":"lifecycle-runtime","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-runtime"},{"artifactId":{"name":"room-ktx","group":"androidx.room","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"room-ktx"},{"artifactId":{"name":"room-runtime","group":"androidx.room","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"room-runtime"},{"artifactId":{"name":"room-common","group":"androidx.room","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"room-common"},{"artifactId":{"name":"sqlite-framework","group":"androidx.sqlite","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"sqlite-framework"},{"artifactId":{"name":"sqlite","group":"androidx.sqlite","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"sqlite"},{"artifactId":{"name":"cardview","group":"androidx.cardview","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"cardview"},{"artifactId":{"name":"exifinterface","group":"androidx.exifinterface","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"exifinterface"},{"artifactId":{"name":"gifdecoder","group":"com.github.bumptech.glide","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"gifdecoder"},{"artifactId":{"name":"paging-common-ktx","group":"androidx.paging","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"paging-common-ktx"},{"artifactId":{"name":"constraintlayout-core","group":"androidx.constraintlayout","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://tools.android.com","libraryName":"constraintlayout-core"},{"artifactId":{"name":"databinding-ktx","group":"androidx.databinding","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","libraryName":"databinding-ktx"},{"artifactId":{"name":"lifecycle-process","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-process"},{"artifactId":{"name":"lifecycle-service","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-service"},{"artifactId":{"name":"work-runtime-ktx","group":"androidx.work","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/work#2.5.0","libraryName":"work-runtime-ktx"},{"artifactId":{"name":"work-runtime","group":"androidx.work","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/work#2.5.0","libraryName":"work-runtime"},{"artifactId":{"name":"paging-common","group":"androidx.paging","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"paging-common"},{"artifactId":{"name":"lifecycle-livedata","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-livedata"},{"artifactId":{"name":"lifecycle-livedata-core-ktx","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"lifecycle-livedata-core-ktx"},{"artifactId":{"name":"lifecycle-livedata-core","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-livedata-core"},{"artifactId":{"name":"core-runtime","group":"androidx.arch.core","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"core-runtime"},{"artifactId":{"name":"core-common","group":"androidx.arch.core","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"core-common"},{"artifactId":{"name":"lifecycle-common","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-common"},{"artifactId":{"name":"documentfile","group":"androidx.documentfile","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"documentfile"},{"artifactId":{"name":"localbroadcastmanager","group":"androidx.localbroadcastmanager","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"localbroadcastmanager"},{"artifactId":{"name":"print","group":"androidx.print","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"print"},{"artifactId":{"name":"annotation","group":"androidx.annotation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"annotation"},{"artifactId":{"name":"converter-gson","group":"com.squareup.retrofit2","version":"+"},"copyrightHolder":"Square, Inc.","copyrightStatement":"Copyright © Square, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/square/retrofit","libraryName":"converter-gson"},{"artifactId":{"name":"adapter-rxjava3","group":"com.squareup.retrofit2","version":"+"},"copyrightHolder":"Square, Inc.","copyrightStatement":"Copyright © Square, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/square/retrofit","libraryName":"adapter-rxjava3"},{"artifactId":{"name":"retrofit","group":"com.squareup.retrofit2","version":"+"},"copyrightHolder":"Square, Inc.","copyrightStatement":"Copyright © Square, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/square/retrofit","libraryName":"retrofit"},{"artifactId":{"name":"okhttp","group":"com.squareup.okhttp3","version":"+"},"copyrightHolder":"Square, Inc.","copyrightStatement":"Copyright © Square, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://square.github.io/okhttp/","libraryName":"okhttp"},{"artifactId":{"name":"rxandroid","group":"io.reactivex.rxjava3","version":"+"},"copyrightHolder":"Netflix, Inc","copyrightStatement":"Copyright © Netflix, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/ReactiveX/RxAndroid","libraryName":"rxandroid"},{"artifactId":{"name":"rxjava","group":"io.reactivex.rxjava3","version":"+"},"copyrightHolder":"Netflix, Inc.","copyrightStatement":"Copyright © Netflix, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/ReactiveX/RxJava","libraryName":"rxjava"},{"artifactId":{"name":"sparkbutton","group":"com.github.connyduck","version":"+"},"copyrightHolder":"varunest and ConnyDuck","copyrightStatement":"Copyright © varunest and ConnyDuck. All rights reserved.","license":"The Apache License, Version 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/connyduck/sparkbutton","libraryName":"sparkbutton"},{"artifactId":{"name":"recyclerview-integration","group":"com.github.bumptech.glide","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"recyclerview-integration"},{"artifactId":{"name":"google-material-typeface","group":"com.mikepenz","version":"+"},"copyrightHolder":"Mike Penz and contributors","copyrightStatement":"Copyright © Mike Penz and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/mikepenz/Android-Iconics","libraryName":"google-material-typeface"},{"artifactId":{"name":"kotlinx-coroutines-android","group":"org.jetbrains.kotlinx","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/Kotlin/kotlinx.coroutines","libraryName":"kotlinx-coroutines-android"},{"artifactId":{"name":"viewbinding","group":"androidx.databinding","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","libraryName":"viewbinding"},{"artifactId":{"name":"kotlinx-coroutines-core-jvm","group":"org.jetbrains.kotlinx","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/Kotlin/kotlinx.coroutines","libraryName":"kotlinx-coroutines-core-jvm"},{"artifactId":{"name":"okhttp","group":"com.squareup.okhttp","version":"+"},"copyrightHolder":"Square, Inc.","copyrightStatement":"Copyright © Square, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","libraryName":"okhttp"},{"artifactId":{"name":"okio","group":"com.squareup.okio","version":"+"},"copyrightHolder":"Square, Inc.","copyrightStatement":"Copyright © Square, Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/square/okio/","libraryName":"okio"},{"artifactId":{"name":"kotlin-stdlib","group":"org.jetbrains.kotlin","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://kotlinlang.org/","libraryName":"kotlin-stdlib"},{"artifactId":{"name":"listenablefuture","group":"com.google.guava","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","libraryName":"listenablefuture"},{"artifactId":{"name":"annotation-experimental","group":"androidx.annotation","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"annotation-experimental"},{"artifactId":{"name":"auto-value-annotations","group":"com.google.auto.value","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","libraryName":"auto-value-annotations"},{"artifactId":{"name":"dagger","group":"com.google.dagger","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Apache 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/google/dagger","libraryName":"dagger"},{"artifactId":{"name":"dagger-lint-aar","group":"com.google.dagger","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Apache 2.0","licenseUrl":"https://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/google/dagger","libraryName":"dagger-lint-aar"},{"artifactId":{"name":"javax.inject","group":"javax.inject","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://code.google.com/p/atinject/","libraryName":"javax.inject"},{"artifactId":{"name":"gson","group":"com.google.code.gson","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","normalizedLicense":"apache2","libraryName":"gson"},{"artifactId":{"name":"disklrucache","group":"com.github.bumptech.glide","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"disklrucache"},{"artifactId":{"name":"annotations","group":"com.github.bumptech.glide","version":"+"},"copyrightHolder":"Google Inc.","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"Simplified BSD License","licenseUrl":"http://www.opensource.org/licenses/bsd-license","normalizedLicense":"bsd_2_clauses","url":"https://github.com/bumptech/glide","libraryName":"annotations"},{"artifactId":{"name":"kotlin-stdlib-common","group":"org.jetbrains.kotlin","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://kotlinlang.org/","libraryName":"kotlin-stdlib-common"},{"artifactId":{"name":"annotations","group":"org.jetbrains","version":"+"},"copyrightHolder":"JetBrains s.r.o. and contributors","copyrightStatement":"Copyright © JetBrains s.r.o. and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://www.jetbrains.org","libraryName":"annotations"},{"artifactId":{"name":"gridlayout","group":"androidx.gridlayout","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"gridlayout"},{"artifactId":{"name":"preference-ktx","group":"androidx.preference","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx","libraryName":"preference-ktx"},{"artifactId":{"name":"databinding-adapters","group":"androidx.databinding","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","libraryName":"databinding-adapters"},{"artifactId":{"name":"databinding-runtime","group":"androidx.databinding","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","libraryName":"databinding-runtime"},{"artifactId":{"name":"databinding-common","group":"androidx.databinding","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/studio","libraryName":"databinding-common"},{"artifactId":{"name":"lifecycle-common-java8","group":"androidx.lifecycle","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/topic/libraries/architecture/index.html","libraryName":"lifecycle-common-java8"},{"artifactId":{"name":"circleindicator","group":"me.relex","version":"+"},"copyrightHolder":"relex and contributors","copyrightStatement":"Copyright © relex and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/ongakuer/CircleIndicator","libraryName":"circleindicator"},{"artifactId":{"name":"dynamicanimation","group":"androidx.dynamicanimation","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"dynamicanimation"},{"artifactId":{"name":"savedstate-ktx","group":"androidx.savedstate","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/savedstate#1.1.0","libraryName":"savedstate-ktx"},{"artifactId":{"name":"tracing","group":"androidx.tracing","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/tracing#1.0.0","libraryName":"tracing"},{"artifactId":{"name":"emoji2-views-helper","group":"androidx.emoji2","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/emoji2#1.0.0","libraryName":"emoji2-views-helper"},{"artifactId":{"name":"emoji2","group":"androidx.emoji2","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/emoji2#1.0.0","libraryName":"emoji2"},{"artifactId":{"name":"resourceinspection-annotation","group":"androidx.resourceinspection","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/resourceinspection#1.0.0","libraryName":"resourceinspection-annotation"},{"artifactId":{"name":"room-paging","group":"androidx.room","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/room#2.4.1","libraryName":"room-paging"},{"artifactId":{"name":"window","group":"androidx.window","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/window#1.0.0","libraryName":"window"},{"artifactId":{"name":"animated-vector-drawable","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"animated-vector-drawable"},{"artifactId":{"name":"appcompat-v7","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"appcompat-v7"},{"artifactId":{"name":"support-annotations","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-annotations"},{"artifactId":{"name":"support-compat","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-compat"},{"artifactId":{"name":"support-core-ui","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-core-ui"},{"artifactId":{"name":"support-core-utils","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-core-utils"},{"artifactId":{"name":"support-fragment","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-fragment"},{"artifactId":{"name":"support-media-compat","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-media-compat"},{"artifactId":{"name":"support-v4","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-v4"},{"artifactId":{"name":"support-vector-drawable","group":"com.android.support","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"support-vector-drawable"},{"artifactId":{"name":"media2-widget","group":"androidx.media2","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/media2","libraryName":"media2-widget"},{"artifactId":{"name":"palette","group":"androidx.palette","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"http://developer.android.com/tools/extras/support-library.html","libraryName":"palette"},{"artifactId":{"name":"media2-player","group":"androidx.media2","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/media2","libraryName":"media2-player"},{"artifactId":{"name":"media2-session","group":"androidx.media2","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/media2","libraryName":"media2-session"},{"artifactId":{"name":"media2-common","group":"androidx.media2","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/media2","libraryName":"media2-common"},{"artifactId":{"name":"media2-exoplayer","group":"androidx.media2","version":"+"},"copyrightHolder":"Google Inc","copyrightStatement":"Copyright © Google Inc. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://developer.android.com/jetpack/androidx/releases/media2","libraryName":"media2-exoplayer"},{"artifactId":{"name":"subsampling-scale-image-view-androidx","group":"com.davemorrissey.labs","version":"+"},"copyrightHolder":"David Morrissey and contributors","copyrightStatement":"Copyright © David Morrissey and contributors. All rights reserved.","license":"The Apache Software License, Version 2.0","licenseUrl":"http://www.apache.org/licenses/LICENSE-2.0.txt","normalizedLicense":"apache2","url":"https://github.com/davemorrissey/subsampling-scale-image-view","libraryName":"subsampling-scale-image-view-androidx"},{"artifactId":{"name":"ffmpeg-kit-min","group":"com.arthenica","version":"+"},"copyrightHolder":"Taner Şener","copyrightStatement":"Copyright © Taner Şener. All rights reserved.","license":"GNU Lesser General Public License, Version 3","licenseUrl":"https://www.gnu.org/licenses/lgpl-3.0.txt","normalizedLicense":"lgpl3","url":"https://github.com/tanersener/ffmpeg-kit","libraryName":"ffmpeg-kit-min"},{"artifactId":{"name":"smart-exception-java","group":"com.arthenica","version":"+"},"copyrightHolder":"Taner Şener","copyrightStatement":"Copyright © Taner Şener. All rights reserved.","license":"The 3-Clause BSD License","licenseUrl":"https://opensource.org/licenses/BSD-3-Clause","normalizedLicense":"bsd_3_clauses","url":"https://github.com/tanersener/smart-exception","libraryName":"smart-exception-java"},{"artifactId":{"name":"smart-exception-common","group":"com.arthenica","version":"+"},"copyrightHolder":"Taner Şener","copyrightStatement":"Copyright © Taner Şener. All rights reserved.","license":"The 3-Clause BSD License","licenseUrl":"https://opensource.org/licenses/BSD-3-Clause","normalizedLicense":"bsd_3_clauses","url":"https://github.com/tanersener/smart-exception","libraryName":"smart-exception-common"}]}
\ No newline at end of file