Migrated parts from SubsonicTabActivity, fixed theme changes

This commit is contained in:
Nite 2021-02-06 11:50:57 +01:00
parent a395bd6feb
commit f0917820cb
No known key found for this signature in database
GPG Key ID: 1D1AD59B1C6386C1
4 changed files with 123 additions and 273 deletions

View File

@ -101,8 +101,6 @@ public class SubsonicTabActivity extends ResultActivity
@Override
protected void onCreate(Bundle bundle)
{
setUncaughtExceptionHandler();
Util.applyTheme(this);
super.onCreate(bundle);
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)
{
this.runOnUiThread(new Runnable()
@ -682,18 +501,6 @@ public class SubsonicTabActivity extends ResultActivity
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)
{
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
protected void onRestoreInstanceState(Bundle inState)
{

View File

@ -4,10 +4,12 @@ import android.app.AlertDialog
import android.app.SearchManager
import android.content.Intent
import android.content.res.Resources
import android.media.AudioManager
import android.os.Bundle
import android.preference.PreferenceManager
import android.provider.MediaStore
import android.provider.SearchRecentSuggestions
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
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.viewmodel.ext.android.viewModel
import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.provider.SearchSuggestionProvider
import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.SubsonicUncaughtExceptionHandler
import org.moire.ultrasonic.util.Util
import timber.log.Timber
@ -39,6 +43,11 @@ import timber.log.Timber
* A simple activity demonstrating use of a NavHostFragment with a navigation drawer.
*/
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 val serverSettingsModel: ServerSettingsModel by viewModel()
private val lifecycleSupport: MediaPlayerLifecycleSupport by inject()
@ -48,7 +57,12 @@ class NavigationActivity : AppCompatActivity() {
private var infoDialogDisplayed = false
override fun onCreate(savedInstanceState: Bundle?) {
setUncaughtExceptionHandler()
Util.applyTheme(this)
super.onCreate(savedInstanceState)
volumeControlStream = AudioManager.STREAM_MUSIC
setContentView(R.layout.navigation_activity)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
@ -78,6 +92,15 @@ class NavigationActivity : AppCompatActivity() {
Integer.toString(destination.id)
}
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
@ -91,6 +114,49 @@ class NavigationActivity : AppCompatActivity() {
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) {
val sideNavView = findViewById<NavigationView>(R.id.nav_view)
sideNavView?.setupWithNavController(navController)
@ -107,6 +173,10 @@ class NavigationActivity : AppCompatActivity() {
}
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) {
@ -182,4 +252,11 @@ class NavigationActivity : AppCompatActivity() {
}
}
}
private fun setUncaughtExceptionHandler() {
val handler = Thread.getDefaultUncaughtExceptionHandler()
if (handler !is SubsonicUncaughtExceptionHandler) {
Thread.setDefaultUncaughtExceptionHandler(SubsonicUncaughtExceptionHandler(this))
}
}
}

View File

@ -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)
}
}
}

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<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_selected">@color/selected_color_dark</item>
<item name="star_hollow">@drawable/ic_star_hollow_dark</item>
@ -59,6 +61,8 @@
</style>
<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_selected">@color/selected_color_dark</item>
<item name="star_hollow">@drawable/ic_star_hollow_dark</item>
@ -117,6 +121,8 @@
</style>
<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_selected">@color/selected_color_light</item>
<item name="star_hollow">@drawable/ic_star_hollow_light</item>