mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-11 18:43:49 +00:00
SES-697 - Add loading state when exporting logs (#1402)
* WIP * Fixes #1401 * Cleanup from PR view * Final cleanup * Removed commented line of code & re-ordered comment * Addressed PR feedback * Re-allowed loading of avatars to throw exceptions rather than return null on failure --------- Co-authored-by: = <=>
This commit is contained in:
parent
fef965bcb5
commit
d3c8635748
@ -478,9 +478,8 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
Log.d("Loki-Avatar", "Uploading Avatar Finished");
|
Log.d("Loki-Avatar", "Uploading Avatar Finished");
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
});
|
});
|
||||||
} catch (Exception exception) {
|
} catch (Exception e) {
|
||||||
// Do nothing
|
Log.e("Loki-Avatar", "Uploading avatar failed.");
|
||||||
Log.e("Loki-Avatar", "Uploading avatar failed", exception);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,14 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
|
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
|
|
||||||
@ -67,6 +72,19 @@ class HelpSettingsFragment: CorrectedPreferenceFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateExportButtonAndProgressBarUI(exportJobRunning: Boolean) {
|
||||||
|
this.activity?.runOnUiThread(Runnable {
|
||||||
|
// Change export logs button text
|
||||||
|
val exportLogsButton = this.activity?.findViewById(R.id.export_logs_button) as TextView?
|
||||||
|
if (exportLogsButton == null) { Log.w("Loki", "Could not find export logs button view.") }
|
||||||
|
exportLogsButton?.text = if (exportJobRunning) getString(R.string.cancel) else getString(R.string.activity_help_settings__export_logs)
|
||||||
|
|
||||||
|
// Show progress bar
|
||||||
|
val exportProgressBar = this.activity?.findViewById(R.id.export_progress_bar) as ProgressBar?
|
||||||
|
exportProgressBar?.isInvisible = !exportJobRunning
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private fun shareLogs() {
|
private fun shareLogs() {
|
||||||
Permissions.with(this)
|
Permissions.with(this)
|
||||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
@ -76,7 +94,7 @@ class HelpSettingsFragment: CorrectedPreferenceFragment() {
|
|||||||
Toast.makeText(requireActivity(), R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()
|
Toast.makeText(requireActivity(), R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
.onAllGranted {
|
.onAllGranted {
|
||||||
ShareLogsDialog().show(parentFragmentManager,"Share Logs Dialog")
|
ShareLogsDialog(::updateExportButtonAndProgressBarUI).show(parentFragmentManager,"Share Logs Dialog")
|
||||||
}
|
}
|
||||||
.execute()
|
.execute()
|
||||||
}
|
}
|
||||||
|
@ -11,55 +11,73 @@ import android.os.Bundle
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
import network.loki.messenger.BuildConfig
|
import network.loki.messenger.BuildConfig
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
|
|
||||||
import org.session.libsignal.utilities.ExternalStorageUtil
|
import org.session.libsignal.utilities.ExternalStorageUtil
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.createSessionDialog
|
import org.thoughtcrime.securesms.createSessionDialog
|
||||||
import org.thoughtcrime.securesms.util.FileProviderUtil
|
import org.thoughtcrime.securesms.util.FileProviderUtil
|
||||||
import org.thoughtcrime.securesms.util.StreamUtil
|
import org.thoughtcrime.securesms.util.StreamUtil
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.Objects
|
import java.util.Objects
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class ShareLogsDialog : DialogFragment() {
|
|
||||||
|
|
||||||
|
class ShareLogsDialog(private val updateCallback: (Boolean)->Unit): DialogFragment() {
|
||||||
|
|
||||||
|
private val TAG = "ShareLogsDialog"
|
||||||
private var shareJob: Job? = null
|
private var shareJob: Job? = null
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog {
|
||||||
title(R.string.dialog_share_logs_title)
|
title(R.string.dialog_share_logs_title)
|
||||||
text(R.string.dialog_share_logs_explanation)
|
text(R.string.dialog_share_logs_explanation)
|
||||||
button(R.string.share, dismiss = false) { shareLogs() }
|
button(R.string.share, dismiss = false) { runShareLogsJob() }
|
||||||
cancelButton { dismiss() }
|
cancelButton { updateCallback(false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shareLogs() {
|
// If the share logs dialog loses focus the job gets cancelled so we'll update the UI state
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
updateCallback(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runShareLogsJob() {
|
||||||
|
// Cancel any existing share job that might already be running to start anew
|
||||||
shareJob?.cancel()
|
shareJob?.cancel()
|
||||||
|
|
||||||
|
updateCallback(true)
|
||||||
|
|
||||||
shareJob = lifecycleScope.launch(Dispatchers.IO) {
|
shareJob = lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val persistentLogger = ApplicationContext.getInstance(context).persistentLogger
|
val persistentLogger = ApplicationContext.getInstance(context).persistentLogger
|
||||||
try {
|
try {
|
||||||
|
Log.d(TAG, "Starting share logs job...")
|
||||||
|
|
||||||
val context = requireContext()
|
val context = requireContext()
|
||||||
val outputUri: Uri = ExternalStorageUtil.getDownloadUri()
|
val outputUri: Uri = ExternalStorageUtil.getDownloadUri()
|
||||||
val mediaUri = getExternalFile()
|
val mediaUri = getExternalFile() ?: return@launch
|
||||||
if (mediaUri == null) {
|
|
||||||
// show toast saying media saved
|
|
||||||
dismiss()
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val inputStream = persistentLogger.logs.get().byteInputStream()
|
val inputStream = persistentLogger.logs.get().byteInputStream()
|
||||||
val updateValues = ContentValues()
|
val updateValues = ContentValues()
|
||||||
|
|
||||||
|
// Add details into the output or media files as appropriate
|
||||||
if (outputUri.scheme == ContentResolver.SCHEME_FILE) {
|
if (outputUri.scheme == ContentResolver.SCHEME_FILE) {
|
||||||
FileOutputStream(mediaUri.path).use { outputStream ->
|
FileOutputStream(mediaUri.path).use { outputStream ->
|
||||||
StreamUtil.copy(inputStream, outputStream)
|
StreamUtil.copy(inputStream, outputStream)
|
||||||
@ -73,6 +91,7 @@ class ShareLogsDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT > 28) {
|
if (Build.VERSION.SDK_INT > 28) {
|
||||||
updateValues.put(MediaStore.MediaColumns.IS_PENDING, 0)
|
updateValues.put(MediaStore.MediaColumns.IS_PENDING, 0)
|
||||||
}
|
}
|
||||||
@ -95,13 +114,35 @@ class ShareLogsDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
startActivity(Intent.createChooser(shareIntent, getString(R.string.share)))
|
startActivity(Intent.createChooser(shareIntent, getString(R.string.share)))
|
||||||
}
|
}
|
||||||
|
|
||||||
dismiss()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
withContext(Main) {
|
withContext(Main) {
|
||||||
Log.e("Loki", "Error saving logs", e)
|
Log.e("Loki", "Error saving logs", e)
|
||||||
Toast.makeText(context,"Error saving logs", Toast.LENGTH_LONG).show()
|
Toast.makeText(context,"Error saving logs", Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}.also { shareJob ->
|
||||||
|
shareJob.invokeOnCompletion { handler ->
|
||||||
|
// Note: Don't show Toasts here directly - use `withContext(Main)` or such if req'd
|
||||||
|
handler?.message.let { msg ->
|
||||||
|
if (shareJob.isCancelled) {
|
||||||
|
if (msg.isNullOrBlank()) {
|
||||||
|
Log.w(TAG, "Share logs job was cancelled.")
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Share logs job was cancelled. Reason: $msg")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (shareJob.isCompleted) {
|
||||||
|
Log.d(TAG, "Share logs job completed. Msg: $msg")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log.w(TAG, "Share logs job finished while still Active. Msg: $msg")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regardless of the job's success it has now completed so update the UI
|
||||||
|
updateCallback(false)
|
||||||
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,5 +199,4 @@ class ShareLogsDialog : DialogFragment() {
|
|||||||
return context.contentResolver.insert(outputUri, contentValues)
|
return context.contentResolver.insert(outputUri, contentValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,9 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/export_logs_button"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
style="@style/Widget.Session.Button.Common.Filled"
|
style="@style/Widget.Session.Button.Common.Filled"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
@ -11,5 +12,6 @@
|
|||||||
android:paddingHorizontal="@dimen/medium_spacing"
|
android:paddingHorizontal="@dimen/medium_spacing"
|
||||||
android:paddingVertical="12dp"
|
android:paddingVertical="12dp"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
@ -1,23 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/container"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="vertical"
|
android:id="@+id/export_progress_container"
|
||||||
android:layout_width="match_parent"
|
android:orientation="vertical"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:paddingBottom="16dp"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="bottom">
|
android:layout_gravity="bottom" >
|
||||||
|
|
||||||
<ProgressBar android:id="@+id/progress_bar"
|
<ProgressBar
|
||||||
style="?android:attr/progressBarStyleHorizontal"
|
android:id="@+id/export_progress_bar"
|
||||||
android:layout_width="match_parent"
|
style="?android:attr/progressBarStyleHorizontal"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:indeterminate="true"/>
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
<TextView android:id="@+id/progress_text"
|
android:visibility="invisible" />
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
tools:text="1345 messages so far"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -6,39 +6,38 @@
|
|||||||
android:key="export_logs"
|
android:key="export_logs"
|
||||||
android:title="@string/activity_help_settings__report_bug_title"
|
android:title="@string/activity_help_settings__report_bug_title"
|
||||||
android:summary="@string/activity_help_settings__report_bug_summary"
|
android:summary="@string/activity_help_settings__report_bug_summary"
|
||||||
android:widgetLayout="@layout/export_logs_widget"/>
|
android:widgetLayout="@layout/export_logs_widget" />
|
||||||
|
|
||||||
|
<!-- Note: Having this as `android:layout` rather than `android:layoutWidget` allows it to fit the screen width -->
|
||||||
|
<Preference android:layout="@layout/preference_widget_progress" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory>
|
<PreferenceCategory>
|
||||||
<Preference
|
<Preference
|
||||||
android:key="translate_session"
|
android:key="translate_session"
|
||||||
android:title="@string/activity_help_settings__translate_session"
|
android:title="@string/activity_help_settings__translate_session"
|
||||||
android:widgetLayout="@layout/preference_external_link"
|
android:widgetLayout="@layout/preference_external_link" />
|
||||||
/>
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory>
|
<PreferenceCategory>
|
||||||
<Preference
|
<Preference
|
||||||
android:key="feedback"
|
android:key="feedback"
|
||||||
android:title="@string/activity_help_settings__feedback"
|
android:title="@string/activity_help_settings__feedback"
|
||||||
android:widgetLayout="@layout/preference_external_link"
|
android:widgetLayout="@layout/preference_external_link" />
|
||||||
/>
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory>
|
<PreferenceCategory>
|
||||||
<Preference
|
<Preference
|
||||||
android:key="faq"
|
android:key="faq"
|
||||||
android:title="@string/activity_help_settings__faq"
|
android:title="@string/activity_help_settings__faq"
|
||||||
android:widgetLayout="@layout/preference_external_link"
|
android:widgetLayout="@layout/preference_external_link" />
|
||||||
/>
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory>
|
<PreferenceCategory>
|
||||||
<Preference
|
<Preference
|
||||||
android:key="support"
|
android:key="support"
|
||||||
android:title="@string/activity_help_settings__support"
|
android:title="@string/activity_help_settings__support"
|
||||||
android:widgetLayout="@layout/preference_external_link"
|
android:widgetLayout="@layout/preference_external_link" />
|
||||||
/>
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
@ -8,9 +8,11 @@ import androidx.annotation.Nullable;
|
|||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.session.libsession.utilities.Address;
|
import org.session.libsession.utilities.Address;
|
||||||
|
import org.session.libsignal.utilities.Log;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -22,9 +24,9 @@ public class AvatarHelper {
|
|||||||
private static final String AVATAR_DIRECTORY = "avatars";
|
private static final String AVATAR_DIRECTORY = "avatars";
|
||||||
|
|
||||||
public static InputStream getInputStreamFor(@NonNull Context context, @NonNull Address address)
|
public static InputStream getInputStreamFor(@NonNull Context context, @NonNull Address address)
|
||||||
throws IOException
|
throws FileNotFoundException
|
||||||
{
|
{
|
||||||
return new FileInputStream(getAvatarFile(context, address));
|
return new FileInputStream(getAvatarFile(context, address));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<File> getAvatarFiles(@NonNull Context context) {
|
public static List<File> getAvatarFiles(@NonNull Context context) {
|
||||||
|
@ -8,6 +8,7 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import org.session.libsession.utilities.Address;
|
import org.session.libsession.utilities.Address;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
@ -24,7 +25,7 @@ public class ProfileContactPhoto implements ContactPhoto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream openInputStream(Context context) throws IOException {
|
public InputStream openInputStream(Context context) throws FileNotFoundException {
|
||||||
return AvatarHelper.getInputStreamFor(context, address);
|
return AvatarHelper.getInputStreamFor(context, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user