mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-05-11 21:06:35 +03:00
Migrated parts from SubsonicTabActivity, fixed theme changes
This commit is contained in:
parent
a395bd6feb
commit
f0917820cb
@ -101,8 +101,6 @@ public class SubsonicTabActivity extends ResultActivity
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle bundle)
|
protected void onCreate(Bundle bundle)
|
||||||
{
|
{
|
||||||
setUncaughtExceptionHandler();
|
|
||||||
Util.applyTheme(this);
|
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||||
@ -418,185 +416,6 @@ public class SubsonicTabActivity extends ResultActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Dialog onCreateDialog(final int id)
|
|
||||||
{
|
|
||||||
if (id == DIALOG_ASK_FOR_SHARE_DETAILS)
|
|
||||||
{
|
|
||||||
final LayoutInflater layoutInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
|
|
||||||
final View layout = layoutInflater.inflate(R.layout.share_details, (ViewGroup) findViewById(R.id.share_details));
|
|
||||||
|
|
||||||
if (layout != null)
|
|
||||||
{
|
|
||||||
shareDescription = (EditText) layout.findViewById(R.id.share_description);
|
|
||||||
hideDialogCheckBox = (CheckBox) layout.findViewById(R.id.hide_dialog);
|
|
||||||
noExpirationCheckBox = (CheckBox) layout.findViewById(R.id.timeSpanDisableCheckBox);
|
|
||||||
saveAsDefaultsCheckBox = (CheckBox) layout.findViewById(R.id.save_as_defaults);
|
|
||||||
timeSpanPicker = (TimeSpanPicker) layout.findViewById(R.id.date_picker);
|
|
||||||
}
|
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
||||||
builder.setTitle(R.string.share_set_share_options);
|
|
||||||
|
|
||||||
builder.setPositiveButton(R.string.common_save, new DialogInterface.OnClickListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onClick(final DialogInterface dialog, final int clickId)
|
|
||||||
{
|
|
||||||
if (!noExpirationCheckBox.isChecked())
|
|
||||||
{
|
|
||||||
TimeSpan timeSpan = timeSpanPicker.getTimeSpan();
|
|
||||||
TimeSpan now = TimeSpan.getCurrentTime();
|
|
||||||
shareDetails.Expiration = now.add(timeSpan).getTotalMilliseconds();
|
|
||||||
}
|
|
||||||
|
|
||||||
shareDetails.Description = String.valueOf(shareDescription.getText());
|
|
||||||
|
|
||||||
if (hideDialogCheckBox.isChecked())
|
|
||||||
{
|
|
||||||
Util.setShouldAskForShareDetails(SubsonicTabActivity.this, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveAsDefaultsCheckBox.isChecked())
|
|
||||||
{
|
|
||||||
String timeSpanType = timeSpanPicker.getTimeSpanType();
|
|
||||||
int timeSpanAmount = timeSpanPicker.getTimeSpanAmount();
|
|
||||||
Util.setDefaultShareExpiration(SubsonicTabActivity.this, !noExpirationCheckBox.isChecked() && timeSpanAmount > 0 ? String.format("%d:%s", timeSpanAmount, timeSpanType) : "");
|
|
||||||
Util.setDefaultShareDescription(SubsonicTabActivity.this, shareDetails.Description);
|
|
||||||
}
|
|
||||||
|
|
||||||
share();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onClick(final DialogInterface dialog, final int clickId)
|
|
||||||
{
|
|
||||||
shareDetails = null;
|
|
||||||
dialog.cancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.setView(layout);
|
|
||||||
builder.setCancelable(true);
|
|
||||||
|
|
||||||
timeSpanPicker.setTimeSpanDisableText(getResources().getString(R.string.no_expiration));
|
|
||||||
|
|
||||||
noExpirationCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b)
|
|
||||||
{
|
|
||||||
timeSpanPicker.setEnabled(!b);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
String defaultDescription = Util.getDefaultShareDescription(this);
|
|
||||||
String timeSpan = Util.getDefaultShareExpiration(this);
|
|
||||||
|
|
||||||
String[] split = COMPILE.split(timeSpan);
|
|
||||||
|
|
||||||
if (split.length == 2)
|
|
||||||
{
|
|
||||||
int timeSpanAmount = Integer.parseInt(split[0]);
|
|
||||||
String timeSpanType = split[1];
|
|
||||||
|
|
||||||
if (timeSpanAmount > 0)
|
|
||||||
{
|
|
||||||
noExpirationCheckBox.setChecked(false);
|
|
||||||
timeSpanPicker.setEnabled(true);
|
|
||||||
timeSpanPicker.setTimeSpanAmount(String.valueOf(timeSpanAmount));
|
|
||||||
timeSpanPicker.setTimeSpanType(timeSpanType);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
noExpirationCheckBox.setChecked(true);
|
|
||||||
timeSpanPicker.setEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
noExpirationCheckBox.setChecked(true);
|
|
||||||
timeSpanPicker.setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
shareDescription.setText(defaultDescription);
|
|
||||||
|
|
||||||
return builder.create();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return super.onCreateDialog(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createShare(final List<Entry> entries)
|
|
||||||
{
|
|
||||||
boolean askForDetails = Util.getShouldAskForShareDetails(this);
|
|
||||||
|
|
||||||
shareDetails = new ShareDetails();
|
|
||||||
shareDetails.Entries = entries;
|
|
||||||
|
|
||||||
if (askForDetails)
|
|
||||||
{
|
|
||||||
showDialog(DIALOG_ASK_FOR_SHARE_DETAILS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
shareDetails.Description = Util.getDefaultShareDescription(this);
|
|
||||||
shareDetails.Expiration = TimeSpan.getCurrentTime().add(Util.getDefaultShareExpirationInMillis(this)).getTotalMilliseconds();
|
|
||||||
share();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void share()
|
|
||||||
{
|
|
||||||
BackgroundTask<Share> task = new TabActivityBackgroundTask<Share>(this, true)
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected Share doInBackground() throws Throwable
|
|
||||||
{
|
|
||||||
List<String> ids = new ArrayList<String>();
|
|
||||||
|
|
||||||
if (shareDetails.Entries.isEmpty())
|
|
||||||
{
|
|
||||||
ids.add(getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (Entry entry : shareDetails.Entries)
|
|
||||||
{
|
|
||||||
ids.add(entry.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this);
|
|
||||||
|
|
||||||
long timeInMillis = 0;
|
|
||||||
|
|
||||||
if (shareDetails.Expiration != 0)
|
|
||||||
{
|
|
||||||
timeInMillis = shareDetails.Expiration;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Share> shares = musicService.createShare(ids, shareDetails.Description, timeInMillis, SubsonicTabActivity.this, this);
|
|
||||||
return shares.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void done(Share result)
|
|
||||||
{
|
|
||||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
|
||||||
intent.setType("text/plain");
|
|
||||||
intent.putExtra(Intent.EXTRA_TEXT, String.format("%s\n\n%s", Util.getShareGreeting(SubsonicTabActivity.this), result.getUrl()));
|
|
||||||
startActivity(Intent.createChooser(intent, getResources().getString(R.string.share_via)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
task.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnTouchListenerOnUiThread(final View view, final OnTouchListener listener)
|
public void setOnTouchListenerOnUiThread(final View view, final OnTouchListener listener)
|
||||||
{
|
{
|
||||||
this.runOnUiThread(new Runnable()
|
this.runOnUiThread(new Runnable()
|
||||||
@ -682,18 +501,6 @@ public class SubsonicTabActivity extends ResultActivity
|
|||||||
return mediaPlayerControllerLazy.getValue();
|
return mediaPlayerControllerLazy.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void warnIfNetworkOrStorageUnavailable()
|
|
||||||
{
|
|
||||||
if (!Util.isExternalStoragePresent())
|
|
||||||
{
|
|
||||||
Util.toast(this, R.string.select_album_no_sdcard);
|
|
||||||
}
|
|
||||||
else if (!ActiveServerProvider.Companion.isOffline(this) && !Util.isNetworkConnected(this))
|
|
||||||
{
|
|
||||||
Util.toast(this, R.string.select_album_no_network);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setActionBarDisplayHomeAsUp(boolean enabled)
|
protected void setActionBarDisplayHomeAsUp(boolean enabled)
|
||||||
{
|
{
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
@ -704,86 +511,6 @@ public class SubsonicTabActivity extends ResultActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setActionBarSubtitle(CharSequence title)
|
|
||||||
{
|
|
||||||
ActionBar actionBar = getSupportActionBar();
|
|
||||||
|
|
||||||
if (actionBar != null)
|
|
||||||
{
|
|
||||||
actionBar.setSubtitle(title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setActionBarSubtitle(int id)
|
|
||||||
{
|
|
||||||
ActionBar actionBar = getSupportActionBar();
|
|
||||||
|
|
||||||
if (actionBar != null)
|
|
||||||
{
|
|
||||||
actionBar.setSubtitle(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUncaughtExceptionHandler()
|
|
||||||
{
|
|
||||||
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
|
||||||
if (!(handler instanceof SubsonicUncaughtExceptionHandler))
|
|
||||||
{
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler(new SubsonicUncaughtExceptionHandler(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs the stack trace of uncaught exceptions to a file on the SD card.
|
|
||||||
*/
|
|
||||||
private static class SubsonicUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler
|
|
||||||
{
|
|
||||||
|
|
||||||
private final Thread.UncaughtExceptionHandler defaultHandler;
|
|
||||||
private final Context context;
|
|
||||||
private static final String filename = "ultrasonic-stacktrace.txt";
|
|
||||||
|
|
||||||
private SubsonicUncaughtExceptionHandler(Context context)
|
|
||||||
{
|
|
||||||
this.context = context;
|
|
||||||
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void uncaughtException(Thread thread, Throwable throwable)
|
|
||||||
{
|
|
||||||
File file = null;
|
|
||||||
PrintWriter printWriter = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
file = new File(FileUtil.getUltrasonicDirectory(context), filename);
|
|
||||||
printWriter = new PrintWriter(file);
|
|
||||||
|
|
||||||
String logMessage = String.format(
|
|
||||||
"Android API level: %s\nUltrasonic version name: %s\nUltrasonic version code: %s\n\n",
|
|
||||||
Build.VERSION.SDK_INT, Util.getVersionName(context), Util.getVersionCode(context));
|
|
||||||
|
|
||||||
printWriter.println(logMessage);
|
|
||||||
throwable.printStackTrace(printWriter);
|
|
||||||
Timber.e(throwable, "Uncaught Exception! %s", logMessage);
|
|
||||||
Timber.i("Stack trace written to %s", file);
|
|
||||||
}
|
|
||||||
catch (Throwable x)
|
|
||||||
{
|
|
||||||
Timber.e(x, "Failed to write stack trace to %s", file);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Util.close(printWriter);
|
|
||||||
if (defaultHandler != null)
|
|
||||||
{
|
|
||||||
defaultHandler.uncaughtException(thread, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onRestoreInstanceState(Bundle inState)
|
protected void onRestoreInstanceState(Bundle inState)
|
||||||
{
|
{
|
||||||
|
@ -4,10 +4,12 @@ import android.app.AlertDialog
|
|||||||
import android.app.SearchManager
|
import android.app.SearchManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.media.AudioManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.provider.SearchRecentSuggestions
|
import android.provider.SearchRecentSuggestions
|
||||||
|
import android.view.KeyEvent
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@ -25,12 +27,14 @@ import com.google.android.material.navigation.NavigationView
|
|||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.koin.android.viewmodel.ext.android.viewModel
|
import org.koin.android.viewmodel.ext.android.viewModel
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
|
||||||
import org.moire.ultrasonic.provider.SearchSuggestionProvider
|
import org.moire.ultrasonic.provider.SearchSuggestionProvider
|
||||||
import org.moire.ultrasonic.service.MediaPlayerController
|
import org.moire.ultrasonic.service.MediaPlayerController
|
||||||
import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport
|
import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport
|
||||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||||
import org.moire.ultrasonic.util.Constants
|
import org.moire.ultrasonic.util.Constants
|
||||||
import org.moire.ultrasonic.util.FileUtil
|
import org.moire.ultrasonic.util.FileUtil
|
||||||
|
import org.moire.ultrasonic.util.SubsonicUncaughtExceptionHandler
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@ -39,6 +43,11 @@ import timber.log.Timber
|
|||||||
* A simple activity demonstrating use of a NavHostFragment with a navigation drawer.
|
* A simple activity demonstrating use of a NavHostFragment with a navigation drawer.
|
||||||
*/
|
*/
|
||||||
class NavigationActivity : AppCompatActivity() {
|
class NavigationActivity : AppCompatActivity() {
|
||||||
|
var chatMenuItem: MenuItem? = null
|
||||||
|
var bookmarksMenuItem: MenuItem? = null
|
||||||
|
var sharesMenuItem: MenuItem? = null
|
||||||
|
private var theme: String? = null
|
||||||
|
|
||||||
private lateinit var appBarConfiguration : AppBarConfiguration
|
private lateinit var appBarConfiguration : AppBarConfiguration
|
||||||
private val serverSettingsModel: ServerSettingsModel by viewModel()
|
private val serverSettingsModel: ServerSettingsModel by viewModel()
|
||||||
private val lifecycleSupport: MediaPlayerLifecycleSupport by inject()
|
private val lifecycleSupport: MediaPlayerLifecycleSupport by inject()
|
||||||
@ -48,7 +57,12 @@ class NavigationActivity : AppCompatActivity() {
|
|||||||
private var infoDialogDisplayed = false
|
private var infoDialogDisplayed = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
setUncaughtExceptionHandler()
|
||||||
|
Util.applyTheme(this)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
volumeControlStream = AudioManager.STREAM_MUSIC
|
||||||
setContentView(R.layout.navigation_activity)
|
setContentView(R.layout.navigation_activity)
|
||||||
|
|
||||||
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
||||||
@ -78,6 +92,15 @@ class NavigationActivity : AppCompatActivity() {
|
|||||||
Integer.toString(destination.id)
|
Integer.toString(destination.id)
|
||||||
}
|
}
|
||||||
Timber.d("Navigated to $dest")
|
Timber.d("Navigated to $dest")
|
||||||
|
|
||||||
|
// TODO: Maybe we can find a better place for theme change. Currently the change occures when navigating between fragments
|
||||||
|
// but theoretically Settings could request a Navigation Activity recreate instantly when the theme setting changes
|
||||||
|
// Make sure to update theme if it has changed
|
||||||
|
if (theme == null) theme = Util.getTheme(this)
|
||||||
|
else if (theme != Util.getTheme(this)) {
|
||||||
|
theme = Util.getTheme(this)
|
||||||
|
recreate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine first run and migrate server settings to DB as early as possible
|
// Determine first run and migrate server settings to DB as early as possible
|
||||||
@ -91,6 +114,49 @@ class NavigationActivity : AppCompatActivity() {
|
|||||||
showInfoDialog(showWelcomeScreen)
|
showInfoDialog(showWelcomeScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
val visibility = !isOffline(this)
|
||||||
|
chatMenuItem?.isVisible = visibility
|
||||||
|
bookmarksMenuItem?.isVisible = visibility
|
||||||
|
sharesMenuItem?.isVisible = visibility
|
||||||
|
|
||||||
|
Util.registerMediaButtonEventReceiver(this, false)
|
||||||
|
// Lifecycle support's constructor registers some event receivers so it should be created early
|
||||||
|
lifecycleSupport.onCreate()
|
||||||
|
|
||||||
|
// TODO: Implement NowPlaying as a Fragment
|
||||||
|
// This must be filled here because onCreate is called before the derived objects would call setContentView
|
||||||
|
//getNowPlayingView()
|
||||||
|
|
||||||
|
if (!SubsonicTabActivity.nowPlayingHidden) {
|
||||||
|
//showNowPlaying()
|
||||||
|
} else {
|
||||||
|
//hideNowPlaying()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
Util.unregisterMediaButtonEventReceiver(this, false)
|
||||||
|
super.onDestroy()
|
||||||
|
|
||||||
|
// TODO: Handle NowPlaying if necessary
|
||||||
|
//nowPlayingView = null
|
||||||
|
imageLoaderProvider.clearImageLoader()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
|
val isVolumeDown = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|
||||||
|
val isVolumeUp = keyCode == KeyEvent.KEYCODE_VOLUME_UP
|
||||||
|
val isVolumeAdjust = isVolumeDown || isVolumeUp
|
||||||
|
val isJukebox = mediaPlayerController.isJukeboxEnabled
|
||||||
|
if (isVolumeAdjust && isJukebox) {
|
||||||
|
mediaPlayerController.adjustJukeboxVolume(isVolumeUp)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.onKeyDown(keyCode, event)
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupNavigationMenu(navController: NavController) {
|
private fun setupNavigationMenu(navController: NavController) {
|
||||||
val sideNavView = findViewById<NavigationView>(R.id.nav_view)
|
val sideNavView = findViewById<NavigationView>(R.id.nav_view)
|
||||||
sideNavView?.setupWithNavController(navController)
|
sideNavView?.setupWithNavController(navController)
|
||||||
@ -107,6 +173,10 @@ class NavigationActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chatMenuItem = sideNavView.menu.findItem(R.id.menu_chat)
|
||||||
|
bookmarksMenuItem = sideNavView.menu.findItem(R.id.menu_bookmarks)
|
||||||
|
sharesMenuItem = sideNavView.menu.findItem(R.id.menu_shares)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupActionBar(navController: NavController, appBarConfig: AppBarConfiguration) {
|
private fun setupActionBar(navController: NavController, appBarConfig: AppBarConfiguration) {
|
||||||
@ -182,4 +252,11 @@ class NavigationActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setUncaughtExceptionHandler() {
|
||||||
|
val handler = Thread.getDefaultUncaughtExceptionHandler()
|
||||||
|
if (handler !is SubsonicUncaughtExceptionHandler) {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(SubsonicUncaughtExceptionHandler(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package org.moire.ultrasonic.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import java.io.PrintWriter
|
||||||
|
|
||||||
|
private const val filename = "ultrasonic-stacktrace.txt"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs the stack trace of uncaught exceptions to a file on the SD card.
|
||||||
|
*/
|
||||||
|
class SubsonicUncaughtExceptionHandler (
|
||||||
|
private val context: Context
|
||||||
|
) : Thread.UncaughtExceptionHandler {
|
||||||
|
private val defaultHandler: Thread.UncaughtExceptionHandler? = Thread.getDefaultUncaughtExceptionHandler()
|
||||||
|
|
||||||
|
override fun uncaughtException(thread: Thread, throwable: Throwable) {
|
||||||
|
var file: File? = null
|
||||||
|
var printWriter: PrintWriter? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
file = File(FileUtil.getUltrasonicDirectory(context), filename)
|
||||||
|
printWriter = PrintWriter(file)
|
||||||
|
val logMessage = String.format(
|
||||||
|
"Android API level: %s\nUltrasonic version name: %s\nUltrasonic version code: %s\n\n",
|
||||||
|
Build.VERSION.SDK_INT, Util.getVersionName(context), Util.getVersionCode(context))
|
||||||
|
printWriter.println(logMessage)
|
||||||
|
throwable.printStackTrace(printWriter)
|
||||||
|
Timber.e(throwable, "Uncaught Exception! %s", logMessage)
|
||||||
|
Timber.i("Stack trace written to %s", file)
|
||||||
|
} catch (x: Throwable) {
|
||||||
|
Timber.e(x, "Failed to write stack trace to %s", file)
|
||||||
|
} finally {
|
||||||
|
Util.close(printWriter)
|
||||||
|
defaultHandler?.uncaughtException(thread, throwable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<style name="UltrasonicTheme.Black" parent="Theme.MaterialComponents">
|
<style name="UltrasonicTheme.Black" parent="Theme.MaterialComponents">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
<item name="color_background">@color/background_color_dark</item>
|
<item name="color_background">@color/background_color_dark</item>
|
||||||
<item name="color_selected">@color/selected_color_dark</item>
|
<item name="color_selected">@color/selected_color_dark</item>
|
||||||
<item name="star_hollow">@drawable/ic_star_hollow_dark</item>
|
<item name="star_hollow">@drawable/ic_star_hollow_dark</item>
|
||||||
@ -59,6 +61,8 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="UltrasonicTheme" parent="Theme.AppCompat">
|
<style name="UltrasonicTheme" parent="Theme.AppCompat">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
<item name="color_background">@color/background_color_dark</item>
|
<item name="color_background">@color/background_color_dark</item>
|
||||||
<item name="color_selected">@color/selected_color_dark</item>
|
<item name="color_selected">@color/selected_color_dark</item>
|
||||||
<item name="star_hollow">@drawable/ic_star_hollow_dark</item>
|
<item name="star_hollow">@drawable/ic_star_hollow_dark</item>
|
||||||
@ -117,6 +121,8 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="UltrasonicTheme.Light" parent="Theme.AppCompat.Light">
|
<style name="UltrasonicTheme.Light" parent="Theme.AppCompat.Light">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
<item name="color_background">@color/background_color_light</item>
|
<item name="color_background">@color/background_color_light</item>
|
||||||
<item name="color_selected">@color/selected_color_light</item>
|
<item name="color_selected">@color/selected_color_light</item>
|
||||||
<item name="star_hollow">@drawable/ic_star_hollow_light</item>
|
<item name="star_hollow">@drawable/ic_star_hollow_light</item>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user