Add option to report errors via e-mail
This commit is contained in:
parent
9a21cd3ad7
commit
158c322cda
19 changed files with 308 additions and 40 deletions
14
README.md
14
README.md
|
|
@ -27,3 +27,17 @@ Click on the image for the demo video
|
||||||
Here is another Secret Video QR code just for the curious minded
|
Here is another Secret Video QR code just for the curious minded
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
## Note to self
|
||||||
|
|
||||||
|
When checking error messages, `jq` can be a life saver.
|
||||||
|
|
||||||
|
Example getting the stack trace for a specific error message:
|
||||||
|
|
||||||
|
`jq -r '.[].errors[] | select(.message == "Parsing Error") | .stacktrace' errors.json`
|
||||||
|
|
||||||
|
Or getting all htmls if an error happened in the json:
|
||||||
|
`jq '.[] | select(.errors[]?.message == "Parsing Error") | .errors[].html'`
|
||||||
|
|
||||||
|
> Use -r when you want to avoid the JSON string quotes around output.
|
||||||
|
|
@ -102,7 +102,8 @@ dependencies {
|
||||||
|
|
||||||
def glide_version = "4.15.1"
|
def glide_version = "4.15.1"
|
||||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||||
// kapt "com.github.bumptech.glide:compiler:$glide_version"
|
|
||||||
|
implementation 'com.google.code.gson:gson:2.12.1'
|
||||||
|
|
||||||
def okhttp_version = "4.12.0"
|
def okhttp_version = "4.12.0"
|
||||||
implementation "com.squareup.retrofit2:retrofit:2.9.0"
|
implementation "com.squareup.retrofit2:retrofit:2.9.0"
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,16 @@
|
||||||
<service
|
<service
|
||||||
android:name=".ui.service.QueueService"
|
android:name=".ui.service.QueueService"
|
||||||
android:foregroundServiceType="dataSync" />
|
android:foregroundServiceType="dataSync" />
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.provider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
@ -3,6 +3,7 @@ package org.fnives.tiktokdownloader.data.network
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.fnives.tiktokdownloader.errortracking.ErrorTracer
|
||||||
import org.fnives.tiktokdownloader.Logger
|
import org.fnives.tiktokdownloader.Logger
|
||||||
import org.fnives.tiktokdownloader.data.model.VideoInPending
|
import org.fnives.tiktokdownloader.data.model.VideoInPending
|
||||||
import org.fnives.tiktokdownloader.data.model.VideoInSavingIntoFile
|
import org.fnives.tiktokdownloader.data.model.VideoInSavingIntoFile
|
||||||
|
|
@ -28,6 +29,7 @@ class TikTokDownloadRemoteSource(
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
cookieStore.clear()
|
cookieStore.clear()
|
||||||
wrapIntoProperException {
|
wrapIntoProperException {
|
||||||
|
ErrorTracer.startErrorTransaction(videoInPending.url)
|
||||||
delay(delayBeforeRequest) // added just so captcha trigger may not happen
|
delay(delayBeforeRequest) // added just so captcha trigger may not happen
|
||||||
val actualUrl = service.getContentActualUrlAndCookie(videoInPending.url)
|
val actualUrl = service.getContentActualUrlAndCookie(videoInPending.url)
|
||||||
val videoUrl: VideoFileUrl
|
val videoUrl: VideoFileUrl
|
||||||
|
|
@ -43,19 +45,26 @@ class TikTokDownloadRemoteSource(
|
||||||
}
|
}
|
||||||
Logger.logMessage("videoFileUrl found = ${videoUrl.videoFileUrl}")
|
Logger.logMessage("videoFileUrl found = ${videoUrl.videoFileUrl}")
|
||||||
delay(delayBeforeRequest) // added just so captcha trigger may not happen
|
delay(delayBeforeRequest) // added just so captcha trigger may not happen
|
||||||
val response = service.getVideo(videoUrl.videoFileUrl)
|
try {
|
||||||
|
val response = service.getVideo(videoUrl.videoFileUrl)
|
||||||
|
ErrorTracer.cancelErrorTransaction()
|
||||||
|
|
||||||
VideoInSavingIntoFile(
|
VideoInSavingIntoFile(
|
||||||
id = videoInPending.id,
|
id = videoInPending.id,
|
||||||
url = videoInPending.url,
|
url = videoInPending.url,
|
||||||
contentType = response.mediaType?.let {
|
contentType = response.mediaType?.let {
|
||||||
VideoInSavingIntoFile.ContentType(
|
VideoInSavingIntoFile.ContentType(
|
||||||
it.type,
|
it.type,
|
||||||
it.subtype
|
it.subtype
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
byteStream = response.videoInputStream
|
byteStream = response.videoInputStream
|
||||||
)
|
)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
val exceptionName = (throwable as? HtmlException)?.exceptionName ?: "Unknown Error"
|
||||||
|
ErrorTracer.addError("video-stream", "$exceptionName error while service.getVideo", throwable = throwable)
|
||||||
|
throw throwable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,5 +85,7 @@ class TikTokDownloadRemoteSource(
|
||||||
cause = throwable,
|
cause = throwable,
|
||||||
html = (throwable as? HtmlException)?.html.orEmpty()
|
html = (throwable as? HtmlException)?.html.orEmpty()
|
||||||
)
|
)
|
||||||
|
} finally {
|
||||||
|
ErrorTracer.commitErrorTransaction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,4 +3,7 @@ package org.fnives.tiktokdownloader.data.network.exceptions
|
||||||
class CaptchaRequiredException(
|
class CaptchaRequiredException(
|
||||||
message: String? = null, cause: Throwable? = null,
|
message: String? = null, cause: Throwable? = null,
|
||||||
override val html: String,
|
override val html: String,
|
||||||
) : Throwable(message, cause), HtmlException
|
) : Throwable(message, cause), HtmlException {
|
||||||
|
|
||||||
|
override val exceptionName: String get() = "CaptchaRequired"
|
||||||
|
}
|
||||||
|
|
@ -2,4 +2,5 @@ package org.fnives.tiktokdownloader.data.network.exceptions
|
||||||
|
|
||||||
interface HtmlException {
|
interface HtmlException {
|
||||||
val html: String
|
val html: String
|
||||||
|
val exceptionName: String
|
||||||
}
|
}
|
||||||
|
|
@ -3,4 +3,6 @@ package org.fnives.tiktokdownloader.data.network.exceptions
|
||||||
class NetworkException(
|
class NetworkException(
|
||||||
message: String? = null, cause: Throwable? = null,
|
message: String? = null, cause: Throwable? = null,
|
||||||
override val html: String,
|
override val html: String,
|
||||||
) : Throwable(message, cause), HtmlException
|
) : Throwable(message, cause), HtmlException {
|
||||||
|
override val exceptionName: String get() = "Network"
|
||||||
|
}
|
||||||
|
|
@ -5,4 +5,6 @@ import java.io.IOException
|
||||||
class ParsingException(
|
class ParsingException(
|
||||||
message: String? = null, cause: Throwable? = null,
|
message: String? = null, cause: Throwable? = null,
|
||||||
override val html: String,
|
override val html: String,
|
||||||
) : IOException(message, cause), HtmlException
|
) : IOException(message, cause), HtmlException {
|
||||||
|
override val exceptionName: String get() = "Parsing"
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
package org.fnives.tiktokdownloader.data.network.exceptions
|
package org.fnives.tiktokdownloader.data.network.exceptions
|
||||||
|
|
||||||
class VideoDeletedException(override val html: String) : Throwable(),
|
class VideoDeletedException(override val html: String) : Throwable(),
|
||||||
HtmlException
|
HtmlException {
|
||||||
|
override val exceptionName: String get() = "Video Deleted"
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
package org.fnives.tiktokdownloader.data.network.exceptions
|
package org.fnives.tiktokdownloader.data.network.exceptions
|
||||||
|
|
||||||
class VideoPrivateException(override val html: String) : Throwable(),
|
class VideoPrivateException(override val html: String) : Throwable(),
|
||||||
HtmlException
|
HtmlException {
|
||||||
|
override val exceptionName: String get() = "Video Private"
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package org.fnives.tiktokdownloader.data.network.parsing.converter
|
package org.fnives.tiktokdownloader.data.network.parsing.converter
|
||||||
|
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
|
import org.fnives.tiktokdownloader.errortracking.ErrorTracer
|
||||||
import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredException
|
import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredException
|
||||||
|
import org.fnives.tiktokdownloader.data.network.exceptions.HtmlException
|
||||||
import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException
|
import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException
|
||||||
import org.fnives.tiktokdownloader.data.network.exceptions.VideoPrivateException
|
import org.fnives.tiktokdownloader.data.network.exceptions.VideoPrivateException
|
||||||
import org.fnives.tiktokdownloader.data.network.parsing.response.ActualVideoPageUrl
|
import org.fnives.tiktokdownloader.data.network.parsing.response.ActualVideoPageUrl
|
||||||
|
|
@ -28,7 +30,13 @@ class ActualVideoPageUrlConverter(
|
||||||
.split("\"")[0]
|
.split("\"")[0]
|
||||||
|
|
||||||
ActualVideoPageUrl(actualVideoPageUrl, responseBodyAsString)
|
ActualVideoPageUrl(actualVideoPageUrl, responseBodyAsString)
|
||||||
} catch (_: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
|
val exceptionName = (throwable as? HtmlException)?.exceptionName ?: "Unknown Error"
|
||||||
|
ErrorTracer.addError(
|
||||||
|
html = responseBodyAsString,
|
||||||
|
message = "$exceptionName in ActualVideoPageUrlConverter",
|
||||||
|
throwable = throwable
|
||||||
|
)
|
||||||
ActualVideoPageUrl(null, responseBodyAsString)
|
ActualVideoPageUrl(null, responseBodyAsString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package org.fnives.tiktokdownloader.data.network.parsing.converter
|
package org.fnives.tiktokdownloader.data.network.parsing.converter
|
||||||
|
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
|
import org.fnives.tiktokdownloader.errortracking.ErrorTracer
|
||||||
import org.fnives.tiktokdownloader.Logger
|
import org.fnives.tiktokdownloader.Logger
|
||||||
import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredException
|
import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredException
|
||||||
|
import org.fnives.tiktokdownloader.data.network.exceptions.HtmlException
|
||||||
import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException
|
import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException
|
||||||
import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException
|
import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException
|
||||||
import org.fnives.tiktokdownloader.data.network.exceptions.VideoPrivateException
|
import org.fnives.tiktokdownloader.data.network.exceptions.VideoPrivateException
|
||||||
|
|
@ -36,15 +38,25 @@ class VideoFileUrlConverter(
|
||||||
VideoPrivateException::class,
|
VideoPrivateException::class,
|
||||||
)
|
)
|
||||||
private fun convert(responseBody: String): VideoFileUrl {
|
private fun convert(responseBody: String): VideoFileUrl {
|
||||||
val html = responseBody.also(throwIfIsCaptchaResponse::invoke)
|
try {
|
||||||
.also(throwIfVideoIsDeletedResponse::invoke)
|
val html = responseBody.also(throwIfIsCaptchaResponse::invoke)
|
||||||
.also(throwIfVideoIsPrivateResponse::invoke)
|
.also(throwIfVideoIsDeletedResponse::invoke)
|
||||||
val url =
|
.also(throwIfVideoIsPrivateResponse::invoke)
|
||||||
tryToParseDownloadLink(html).also { Logger.logMessage("parsed download link = $it") }
|
val url =
|
||||||
?: tryToParseVideoSrc(html).also { Logger.logMessage("parsed video src = $it") }
|
tryToParseDownloadLink(html).also { Logger.logMessage("parsed download link = $it") }
|
||||||
?: throw IllegalArgumentException("Couldn't parse url from HTML: $html")
|
?: tryToParseVideoSrc(html).also { Logger.logMessage("parsed video src = $it") }
|
||||||
|
?: throw IllegalArgumentException("Couldn't parse url from HTML: $html")
|
||||||
|
|
||||||
return VideoFileUrl(url)
|
return VideoFileUrl(url)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
val exceptionName = (throwable as? HtmlException)?.exceptionName ?: "Unknown Error"
|
||||||
|
ErrorTracer.addError(
|
||||||
|
html = responseBody,
|
||||||
|
message = "$exceptionName in VideoFileUrlConverter",
|
||||||
|
throwable = throwable
|
||||||
|
)
|
||||||
|
throw throwable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
package org.fnives.tiktokdownloader.errortracking
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
|
||||||
|
object ErrorTracer {
|
||||||
|
|
||||||
|
private var errorTransaction: ErrorTransaction? = null
|
||||||
|
private val errorTransactions = mutableListOf<ErrorTransaction>()
|
||||||
|
private val errorListener = mutableListOf<() -> Unit>()
|
||||||
|
|
||||||
|
val hasErrors get() = errorTransactions.isNotEmpty()
|
||||||
|
|
||||||
|
fun startErrorTransaction(url: String) {
|
||||||
|
errorTransaction = ErrorTransaction(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addError(html: String, message: String, throwable: Throwable?) {
|
||||||
|
errorTransaction?.addError(html = html, message = message, throwable = throwable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelErrorTransaction() {
|
||||||
|
errorTransaction = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun commitErrorTransaction() {
|
||||||
|
val needsToNotify = !hasErrors
|
||||||
|
val errorTransaction = errorTransaction
|
||||||
|
if (errorTransaction != null) {
|
||||||
|
errorTransactions.add(errorTransaction)
|
||||||
|
}
|
||||||
|
if (needsToNotify) {
|
||||||
|
val errorListener = errorListener
|
||||||
|
errorListener.forEach {
|
||||||
|
doOnMainThread {
|
||||||
|
it.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun subscribeToHasErrorChanges(listener: () -> Unit): () -> Unit {
|
||||||
|
doOnMainThread {
|
||||||
|
errorListener.add(listener)
|
||||||
|
}
|
||||||
|
return fun() {
|
||||||
|
doOnMainThread {
|
||||||
|
errorListener.remove(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doOnMainThread(action: () -> Unit) {
|
||||||
|
Handler(Looper.getMainLooper()).post { action() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun getErrorAsJSON(): String {
|
||||||
|
val errorTransactions = errorTransactions
|
||||||
|
val gson = Gson()
|
||||||
|
val arrayInner = errorTransactions.joinToString(",", transform = gson::toJson)
|
||||||
|
return "[$arrayInner]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ErrorTransaction(
|
||||||
|
@SerializedName("url") val url: String
|
||||||
|
) {
|
||||||
|
@SerializedName("errors")
|
||||||
|
val errors = mutableListOf<ErrorsDuringTransaction>()
|
||||||
|
|
||||||
|
@SerializedName("type")
|
||||||
|
val errorType = "ErrorTransaction"
|
||||||
|
|
||||||
|
fun addError(html: String, message: String, throwable: Throwable?) {
|
||||||
|
val stacktrace = throwable?.stackTraceToString()
|
||||||
|
|
||||||
|
errors.add(
|
||||||
|
ErrorsDuringTransaction(
|
||||||
|
html = html,
|
||||||
|
message = message,
|
||||||
|
stacktrace = stacktrace,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ErrorsDuringTransaction(
|
||||||
|
@SerializedName("html")
|
||||||
|
val html: String,
|
||||||
|
@SerializedName("message")
|
||||||
|
val message: String,
|
||||||
|
@SerializedName("stacktrace")
|
||||||
|
val stacktrace: String?
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.fnives.tiktokdownloader.errortracking
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object SendErrorsAsEmail {
|
||||||
|
|
||||||
|
fun send(context: Context) {
|
||||||
|
val subfolder = File(context.cacheDir, "errors")
|
||||||
|
if (!subfolder.exists()) {
|
||||||
|
subfolder.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the file inside subfolder
|
||||||
|
val tempFile = File(subfolder, "errors.json")
|
||||||
|
|
||||||
|
// Write content
|
||||||
|
tempFile.writeText(ErrorTracer.getErrorAsJSON())
|
||||||
|
val contentUri = FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
"${context.packageName}.provider",
|
||||||
|
tempFile
|
||||||
|
)
|
||||||
|
|
||||||
|
val emailIntent = Intent(Intent.ACTION_SEND)
|
||||||
|
emailIntent.setType("application/json")
|
||||||
|
emailIntent.putExtra(Intent.EXTRA_EMAIL, arrayOf("projectsupport202@proton.me"))
|
||||||
|
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Reporting Errors from TikTokDownloader")
|
||||||
|
emailIntent.putExtra(Intent.EXTRA_TEXT, "Attached error as JSON")
|
||||||
|
emailIntent.putExtra(Intent.EXTRA_STREAM, contentUri)
|
||||||
|
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
|
||||||
|
context.startActivity(Intent.createChooser(emailIntent, "Report errors in email..."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,9 +3,15 @@ package org.fnives.tiktokdownloader.ui.main.settings
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.widget.SwitchCompat
|
import androidx.appcompat.widget.SwitchCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import org.fnives.tiktokdownloader.errortracking.ErrorTracer
|
||||||
import org.fnives.tiktokdownloader.R
|
import org.fnives.tiktokdownloader.R
|
||||||
import org.fnives.tiktokdownloader.di.provideViewModels
|
import org.fnives.tiktokdownloader.di.provideViewModels
|
||||||
|
import org.fnives.tiktokdownloader.errortracking.SendErrorsAsEmail
|
||||||
|
|
||||||
class SettingsFragment : Fragment(R.layout.fragment_settings) {
|
class SettingsFragment : Fragment(R.layout.fragment_settings) {
|
||||||
|
|
||||||
|
|
@ -24,6 +30,38 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) {
|
||||||
alwaysOpenAppHolder.setOnClickListener {
|
alwaysOpenAppHolder.setOnClickListener {
|
||||||
viewModel.setAlwaysOpenApp(!alwaysOpenAppSwitch.isChecked)
|
viewModel.setAlwaysOpenApp(!alwaysOpenAppSwitch.isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycle.addObserver(ErrorObserver())
|
||||||
|
view.findViewById<View>(R.id.report_error_cta).setOnClickListener {
|
||||||
|
SendErrorsAsEmail.send(it.context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inner class ErrorObserver : LifecycleEventObserver {
|
||||||
|
private var subscription: (() -> Unit)? = null
|
||||||
|
|
||||||
|
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
||||||
|
when (event) {
|
||||||
|
Lifecycle.Event.ON_START -> {
|
||||||
|
val errorCTA = view?.findViewById<View>(R.id.report_error_cta)
|
||||||
|
subscription = ErrorTracer.subscribeToHasErrorChanges {
|
||||||
|
errorCTA?.isVisible = ErrorTracer.hasErrors
|
||||||
|
}
|
||||||
|
errorCTA?.isVisible = ErrorTracer.hasErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
Lifecycle.Event.ON_STOP -> {
|
||||||
|
subscription?.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
Lifecycle.Event.ON_DESTROY -> {
|
||||||
|
source.lifecycle.removeObserver(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -20,26 +20,43 @@
|
||||||
app:cardElevation="@dimen/card_elevation">
|
app:cardElevation="@dimen/card_elevation">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/always_open_app_holder"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:orientation="vertical">
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="@dimen/default_padding">
|
<LinearLayout
|
||||||
|
android:id="@+id/always_open_app_holder"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="@dimen/default_padding">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/user_preference_always_open_app"
|
||||||
|
android:textAppearance="?attr/textAppearanceSubtitle1" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
|
android:id="@+id/always_open_app"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:id="@+id/report_error_cta"
|
||||||
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:text="@string/user_preference_always_open_app"
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:minHeight="@dimen/minimum_touch_target"
|
||||||
|
android:padding="@dimen/default_padding"
|
||||||
|
android:text="@string/report_errors"
|
||||||
android:textAppearance="?attr/textAppearanceSubtitle1" />
|
android:textAppearance="?attr/textAppearanceSubtitle1" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.SwitchCompat
|
|
||||||
android:id="@+id/always_open_app"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<dimen name="activity_horizontal_margin">24dp</dimen>
|
<dimen name="activity_horizontal_margin">24dp</dimen>
|
||||||
|
<dimen name="minimum_touch_target">48dp</dimen>
|
||||||
<dimen name="default_padding">16dp</dimen>
|
<dimen name="default_padding">16dp</dimen>
|
||||||
<dimen name="medium_padding">8dp</dimen>
|
<dimen name="medium_padding">8dp</dimen>
|
||||||
<dimen name="card_elevation">2dp</dimen>
|
<dimen name="card_elevation">2dp</dimen>
|
||||||
|
|
|
||||||
|
|
@ -51,4 +51,5 @@
|
||||||
<string name="could_not_open">Couldn\'t open!</string>
|
<string name="could_not_open">Couldn\'t open!</string>
|
||||||
<string name="settings">Settings</string>
|
<string name="settings">Settings</string>
|
||||||
<string name="user_preference_always_open_app">Always open app when sharing video</string>
|
<string name="user_preference_always_open_app">Always open app when sharing video</string>
|
||||||
|
<string name="report_errors">Report errors via e-mail</string>
|
||||||
</resources>
|
</resources>
|
||||||
6
app/src/main/res/xml/file_paths.xml
Normal file
6
app/src/main/res/xml/file_paths.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<cache-path
|
||||||
|
name="errors"
|
||||||
|
path="errors/" />
|
||||||
|
</paths>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue