ViewImageFragment: replace TouchImageView by BigImageView based on SSIV and with proper GIF support
This commit is contained in:
parent
ef6343faaa
commit
9c29cf1640
|
@ -178,7 +178,9 @@ dependencies {
|
||||||
|
|
||||||
implementation "com.github.connyduck:sparkbutton:4.0.0"
|
implementation "com.github.connyduck:sparkbutton:4.0.0"
|
||||||
|
|
||||||
implementation 'com.github.MikeOrtiz:TouchImageView:3.0.1'
|
implementation 'com.github.piasy:BigImageViewer:1.6.5'
|
||||||
|
implementation 'com.github.piasy:GlideImageLoader:1.6.5'
|
||||||
|
implementation 'com.github.piasy:GlideImageViewFactory:1.6.5'
|
||||||
|
|
||||||
implementation "com.mikepenz:materialdrawer:$materialdrawerVersion"
|
implementation "com.mikepenz:materialdrawer:$materialdrawerVersion"
|
||||||
implementation "com.mikepenz:materialdrawer-iconics:$materialdrawerVersion"
|
implementation "com.mikepenz:materialdrawer-iconics:$materialdrawerVersion"
|
||||||
|
|
|
@ -22,6 +22,8 @@ import android.util.Log
|
||||||
import androidx.emoji.text.EmojiCompat
|
import androidx.emoji.text.EmojiCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
|
import com.github.piasy.biv.BigImageViewer
|
||||||
|
import com.github.piasy.biv.loader.glide.GlideCustomImageLoader
|
||||||
import com.keylesspalace.tusky.components.notifications.NotificationWorkerFactory
|
import com.keylesspalace.tusky.components.notifications.NotificationWorkerFactory
|
||||||
import com.keylesspalace.tusky.di.AppInjector
|
import com.keylesspalace.tusky.di.AppInjector
|
||||||
import com.keylesspalace.tusky.util.*
|
import com.keylesspalace.tusky.util.*
|
||||||
|
@ -73,6 +75,8 @@ class TuskyApplication : Application(), HasAndroidInjector {
|
||||||
RxJavaPlugins.setErrorHandler {
|
RxJavaPlugins.setErrorHandler {
|
||||||
Log.w("RxJava", "undeliverable exception", it)
|
Log.w("RxJava", "undeliverable exception", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BigImageViewer.initialize(GlideCustomImageLoader.with(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
override fun attachBaseContext(base: Context) {
|
||||||
|
|
|
@ -33,9 +33,11 @@ import at.connyduck.sparkbutton.helpers.Utils
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.target.CustomTarget
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
|
import com.github.piasy.biv.loader.glide.GlideCustomImageLoader
|
||||||
|
import com.github.piasy.biv.view.BigImageView
|
||||||
|
import com.github.piasy.biv.view.GlideImageViewFactory
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.util.withLifecycleContext
|
import com.keylesspalace.tusky.util.withLifecycleContext
|
||||||
import com.ortiz.touchview.TouchImageView
|
|
||||||
|
|
||||||
// https://github.com/tootsuite/mastodon/blob/1656663/app/models/media_attachment.rb#L94
|
// https://github.com/tootsuite/mastodon/blob/1656663/app/models/media_attachment.rb#L94
|
||||||
private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 420
|
private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 420
|
||||||
|
@ -50,9 +52,9 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
||||||
dialogLayout.setPadding(padding, padding, padding, padding)
|
dialogLayout.setPadding(padding, padding, padding, padding)
|
||||||
|
|
||||||
dialogLayout.orientation = LinearLayout.VERTICAL
|
dialogLayout.orientation = LinearLayout.VERTICAL
|
||||||
val imageView = TouchImageView(this).apply {
|
val imageView = BigImageView(this)
|
||||||
maxZoom = 6f
|
// imageView.ssiv.maxScale = 6f
|
||||||
}
|
imageView.setImageViewFactory(GlideImageViewFactory())
|
||||||
|
|
||||||
val displayMetrics = DisplayMetrics()
|
val displayMetrics = DisplayMetrics()
|
||||||
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||||
|
@ -98,18 +100,9 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
||||||
|
|
||||||
// Load the image and manually set it into the ImageView because it doesn't have a fixed
|
// Load the image and manually set it into the ImageView because it doesn't have a fixed
|
||||||
// size. Maybe we should limit the size of CustomTarget
|
// size. Maybe we should limit the size of CustomTarget
|
||||||
Glide.with(this)
|
imageView.showImage(previewUri)
|
||||||
.load(previewUri)
|
|
||||||
.into(object : CustomTarget<Drawable>() {
|
|
||||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
|
||||||
|
|
||||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
|
||||||
imageView.setImageDrawable(resource)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun Activity.showFailedCaptionMessage() {
|
private fun Activity.showFailedCaptionMessage() {
|
||||||
Toast.makeText(this, R.string.error_failed_set_caption, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.error_failed_set_caption, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,15 +20,18 @@ import android.animation.AnimatorListenerAdapter
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.DataSource
|
import com.bumptech.glide.load.DataSource
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
import com.bumptech.glide.request.RequestListener
|
import com.bumptech.glide.request.RequestListener
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
|
import com.github.piasy.biv.loader.ImageLoader
|
||||||
|
import com.github.piasy.biv.view.GlideImageViewFactory
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.entity.Attachment
|
import com.keylesspalace.tusky.entity.Attachment
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
|
@ -36,9 +39,12 @@ import com.keylesspalace.tusky.util.visible
|
||||||
import io.reactivex.subjects.BehaviorSubject
|
import io.reactivex.subjects.BehaviorSubject
|
||||||
import kotlinx.android.synthetic.main.activity_view_media.*
|
import kotlinx.android.synthetic.main.activity_view_media.*
|
||||||
import kotlinx.android.synthetic.main.fragment_view_image.*
|
import kotlinx.android.synthetic.main.fragment_view_image.*
|
||||||
|
import java.io.File
|
||||||
|
import java.lang.Exception
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
class ViewImageFragment : ViewMediaFragment() {
|
|
||||||
|
class ViewImageFragment : ViewMediaFragment(), ImageLoader.Callback, View.OnTouchListener {
|
||||||
interface PhotoActionsListener {
|
interface PhotoActionsListener {
|
||||||
fun onBringUp()
|
fun onBringUp()
|
||||||
fun onDismiss()
|
fun onDismiss()
|
||||||
|
@ -47,7 +53,6 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
|
|
||||||
private lateinit var photoActionsListener: PhotoActionsListener
|
private lateinit var photoActionsListener: PhotoActionsListener
|
||||||
private lateinit var toolbar: View
|
private lateinit var toolbar: View
|
||||||
private var transition = BehaviorSubject.create<Unit>()
|
|
||||||
private var shouldStartTransition = false
|
private var shouldStartTransition = false
|
||||||
|
|
||||||
// Volatile: Image requests happen on background thread and we want to see updates to it
|
// Volatile: Image requests happen on background thread and we want to see updates to it
|
||||||
|
@ -65,73 +70,73 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
descriptionView = mediaDescription
|
descriptionView = mediaDescription
|
||||||
photoView.transitionName = url
|
photoView.transitionName = url
|
||||||
startedTransition = false
|
startedTransition = false
|
||||||
loadImageFromNetwork(url, previewUrl, photoView)
|
loadImageFromNetwork(url, previewUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
toolbar = activity!!.toolbar
|
toolbar = activity!!.toolbar
|
||||||
this.transition = BehaviorSubject.create()
|
|
||||||
return inflater.inflate(R.layout.fragment_view_image, container, false)
|
return inflater.inflate(R.layout.fragment_view_image, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var lastY = 0.0f
|
||||||
|
private var swipeStartedWithOneFinger = false
|
||||||
|
private lateinit var gestureDetector : GestureDetector
|
||||||
|
|
||||||
|
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||||
|
// This part is for scaling/translating on vertical move.
|
||||||
|
// We use raw coordinates to get the correct ones during scaling
|
||||||
|
gestureDetector.onTouchEvent(event)
|
||||||
|
|
||||||
|
if(event.pointerCount != 1) {
|
||||||
|
swipeStartedWithOneFinger = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = false
|
||||||
|
|
||||||
|
when(event.action) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
swipeStartedWithOneFinger = true
|
||||||
|
lastY = event.rawY
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||||
|
onGestureEnd()
|
||||||
|
swipeStartedWithOneFinger = false
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
if(swipeStartedWithOneFinger && photoView.ssiv.scale <= photoView.ssiv.minScale) {
|
||||||
|
val diff = event.rawY - lastY
|
||||||
|
// This code is to prevent transformations during page scrolling
|
||||||
|
// If we are already translating or we reached the threshold, then transform.
|
||||||
|
if (photoView.translationY != 0f || abs(diff) > 40) {
|
||||||
|
photoView.translationY += (diff)
|
||||||
|
val scale = (-abs(photoView.translationY) / 720 + 1).coerceAtLeast(0.5f)
|
||||||
|
photoView.scaleY = scale
|
||||||
|
photoView.scaleX = scale
|
||||||
|
lastY = event.rawY
|
||||||
|
}
|
||||||
|
result = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val gestureDetector = GestureDetector(requireContext(), object : GestureDetector.SimpleOnGestureListener() {
|
gestureDetector = GestureDetector(requireContext(), object : GestureDetector.SimpleOnGestureListener() {
|
||||||
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
|
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
|
||||||
onMediaTap()
|
onMediaTap()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var lastY = 0f
|
// photoView.setOnTouchListener(this)
|
||||||
photoView.setOnTouchListener { _, event ->
|
photoView.setImageLoaderCallback(this)
|
||||||
// This part is for scaling/translating on vertical move.
|
photoView.setImageViewFactory(GlideImageViewFactory())
|
||||||
// We use raw coordinates to get the correct ones during scaling
|
|
||||||
var result = true
|
|
||||||
|
|
||||||
gestureDetector.onTouchEvent(event)
|
|
||||||
|
|
||||||
if (event.action == MotionEvent.ACTION_DOWN) {
|
|
||||||
lastY = event.rawY
|
|
||||||
} else if (!photoView.isZoomed && event.action == MotionEvent.ACTION_MOVE) {
|
|
||||||
val diff = event.rawY - lastY
|
|
||||||
// This code is to prevent transformations during page scrolling
|
|
||||||
// If we are already translating or we reached the threshold, then transform.
|
|
||||||
if (photoView.translationY != 0f || abs(diff) > 40) {
|
|
||||||
photoView.translationY += (diff)
|
|
||||||
val scale = (-abs(photoView.translationY) / 720 + 1).coerceAtLeast(0.5f)
|
|
||||||
photoView.scaleY = scale
|
|
||||||
photoView.scaleX = scale
|
|
||||||
lastY = event.rawY
|
|
||||||
}
|
|
||||||
return@setOnTouchListener true
|
|
||||||
} else if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) {
|
|
||||||
onGestureEnd()
|
|
||||||
} else if (event.pointerCount >= 2 || photoView.canScrollHorizontally(1) && photoView.canScrollHorizontally(-1)) {
|
|
||||||
// Starting from here is adapted code from TouchImageView to play nice with pager.
|
|
||||||
|
|
||||||
// Can scroll horizontally checks if there's still a part of the image.
|
|
||||||
// That can be scrolled until you reach the edge multi-touch event.
|
|
||||||
val parent = view.parent
|
|
||||||
result = when (event.action) {
|
|
||||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
|
|
||||||
// Disallow RecyclerView to intercept touch events.
|
|
||||||
parent.requestDisallowInterceptTouchEvent(true)
|
|
||||||
// Disable touch on view
|
|
||||||
false
|
|
||||||
}
|
|
||||||
MotionEvent.ACTION_UP -> {
|
|
||||||
// Allow RecyclerView to intercept touch events.
|
|
||||||
parent.requestDisallowInterceptTouchEvent(false)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
val arguments = this.requireArguments()
|
val arguments = this.requireArguments()
|
||||||
val attachment = arguments.getParcelable<Attachment>(ARG_ATTACHMENT)
|
val attachment = arguments.getParcelable<Attachment>(ARG_ATTACHMENT)
|
||||||
|
@ -181,99 +186,37 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
Glide.with(this).clear(photoView)
|
|
||||||
transition.onComplete()
|
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
photoView.ssiv?.recycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadImageFromNetwork(url: String, previewUrl: String?, photoView: ImageView) {
|
private fun loadImageFromNetwork(url: String, previewUrl: String?) {
|
||||||
val glide = Glide.with(this)
|
photoView.showImage(Uri.parse(previewUrl), Uri.parse(url))
|
||||||
// Request image from the any cache
|
|
||||||
glide
|
|
||||||
.load(url)
|
|
||||||
.dontAnimate()
|
|
||||||
.onlyRetrieveFromCache(true)
|
|
||||||
.let {
|
|
||||||
if (previewUrl != null)
|
|
||||||
it.thumbnail(glide
|
|
||||||
.load(previewUrl)
|
|
||||||
.dontAnimate()
|
|
||||||
.onlyRetrieveFromCache(true)
|
|
||||||
.addListener(ImageRequestListener(true, isThumnailRequest = true)))
|
|
||||||
else it
|
|
||||||
}
|
|
||||||
//Request image from the network on fail load image from cache
|
|
||||||
.error(glide.load(url)
|
|
||||||
.centerInside()
|
|
||||||
.addListener(ImageRequestListener(false, isThumnailRequest = false))
|
|
||||||
)
|
|
||||||
.addListener(ImageRequestListener(true, isThumnailRequest = false))
|
|
||||||
.into(photoView)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun onSuccess(image: File?) {
|
||||||
* We start transition as soon as we think reasonable but we must take care about couple of
|
progressBar?.hide() // Always hide the progress bar on success
|
||||||
* things>
|
photoActionsListener.onBringUp()
|
||||||
* - Do not change image in the middle of transition. It messes up the view.
|
photoView.ssiv?.setOnTouchListener(this)
|
||||||
* - Do not transition for the views which don't require it. Starting transition from
|
}
|
||||||
* multiple fragments does weird things
|
|
||||||
* - Do not wait to transition until the image loads from network
|
|
||||||
*
|
|
||||||
* Preview, cached image, network image, x - failed, o - succeeded
|
|
||||||
* P C N - start transition after...
|
|
||||||
* x x x - the cache fails
|
|
||||||
* x x o - the cache fails
|
|
||||||
* x o o - the cache succeeds
|
|
||||||
* o x o - the preview succeeds. Do not start on cache.
|
|
||||||
* o o o - the preview succeeds. Do not start on cache.
|
|
||||||
*
|
|
||||||
* So start transition after the first success or after anything with the cache
|
|
||||||
*
|
|
||||||
* @param isCacheRequest - is this listener for request image from cache or from the network
|
|
||||||
*/
|
|
||||||
private inner class ImageRequestListener(
|
|
||||||
private val isCacheRequest: Boolean,
|
|
||||||
private val isThumnailRequest: Boolean) : RequestListener<Drawable> {
|
|
||||||
|
|
||||||
override fun onLoadFailed(e: GlideException?, model: Any, target: Target<Drawable>,
|
override fun onFail(error: Exception?) {
|
||||||
isFirstResource: Boolean): Boolean {
|
progressBar?.hide()
|
||||||
// If cache for full image failed complete transition
|
photoActionsListener.onBringUp()
|
||||||
if (isCacheRequest && !isThumnailRequest && shouldStartTransition
|
}
|
||||||
&& !startedTransition) {
|
|
||||||
photoActionsListener.onBringUp()
|
|
||||||
}
|
|
||||||
// Hide progress bar only on fail request from internet
|
|
||||||
if (!isCacheRequest) progressBar?.hide()
|
|
||||||
// We don't want to overwrite preview with null when main image fails to load
|
|
||||||
return !isCacheRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
override fun onCacheHit(imageType: Int, image: File?) {
|
||||||
override fun onResourceReady(resource: Drawable, model: Any, target: Target<Drawable>,
|
}
|
||||||
dataSource: DataSource, isFirstResource: Boolean): Boolean {
|
|
||||||
progressBar?.hide() // Always hide the progress bar on success
|
|
||||||
|
|
||||||
if (!startedTransition || !shouldStartTransition) {
|
override fun onCacheMiss(imageType: Int, image: File?) {
|
||||||
// Set this right away so that we don't have to concurrent post() requests
|
}
|
||||||
startedTransition = true
|
|
||||||
// post() because load() replaces image with null. Sometimes after we set
|
override fun onFinish() {
|
||||||
// the thumbnail.
|
}
|
||||||
photoView.post {
|
|
||||||
target.onResourceReady(resource, null)
|
override fun onProgress(progress: Int) {
|
||||||
if (shouldStartTransition) photoActionsListener.onBringUp()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// This wait for transition. If there's no transition then we should hit
|
|
||||||
// another branch. take() will unsubscribe after we have it to not leak menmory
|
|
||||||
transition
|
|
||||||
.take(1)
|
|
||||||
.subscribe { target.onResourceReady(resource, null) }
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTransitionEnd() {
|
override fun onTransitionEnd() {
|
||||||
this.transition.onNext(Unit)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,12 @@
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true">
|
android:focusable="true">
|
||||||
|
|
||||||
<com.ortiz.touchview.TouchImageView
|
<com.github.piasy.biv.view.BigImageView
|
||||||
android:id="@+id/photoView"
|
android:id="@+id/photoView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent"
|
||||||
|
app:initScaleType="fitCenter"
|
||||||
|
app:optimizeDisplay="false" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
<item android:id="@+id/action_open_in_web"
|
<item android:id="@+id/action_open_in_web"
|
||||||
android:icon="@drawable/ic_exit_to_app_24px"
|
android:icon="@drawable/ic_exit_to_app_24px"
|
||||||
|
android:iconTint="@color/textColorPrimary"
|
||||||
android:title="@string/action_open_in_web"
|
android:title="@string/action_open_in_web"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,12 @@ allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
maven { url "https://jitpack.io" }
|
maven {
|
||||||
|
url "http://dl.bintray.com/piasy/maven"
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
url "https://jitpack.io"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue