Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-guava to v1.6.4

This commit is contained in:
Renovate Bot 2022-09-24 13:59:43 +00:00 committed by birdbird
parent 008a07b438
commit 2b7a3b0488
38 changed files with 355 additions and 605 deletions

View File

@ -19,8 +19,8 @@ constraintLayout = "2.1.4"
multidex = "2.0.1" multidex = "2.0.1"
room = "2.4.3" room = "2.4.3"
kotlin = "1.7.10" kotlin = "1.7.10"
kotlinxCoroutines = "1.6.3-native-mt" kotlinxCoroutines = "1.6.4-native-mt"
kotlinxGuava = "1.6.3" kotlinxGuava = "1.6.4"
viewModelKtx = "2.5.1" viewModelKtx = "2.5.1"
retrofit = "2.9.0" retrofit = "2.9.0"

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 7.2.2" type="baseline" client="gradle" dependencies="false" name="AGP (7.2.2)" variant="all" version="7.2.2"> <issues format="6" by="lint 7.3.0" type="baseline" client="gradle" dependencies="false" name="AGP (7.3.0)" variant="all" version="7.3.0">
<issue <issue
id="PluralsCandidate" id="PluralsCandidate"
@ -737,7 +737,7 @@
errorLine2=" ~~~~~~~~~~~~~"> errorLine2=" ~~~~~~~~~~~~~">
<location <location
file="src/main/res/layout/equalizer_bar.xml" file="src/main/res/layout/equalizer_bar.xml"
line="19" line="26"
column="13"/> column="13"/>
</issue> </issue>

View File

@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>

View File

@ -1,166 +0,0 @@
package org.moire.ultrasonic.util;
import java.util.Calendar;
import java.util.Date;
public class TimeSpan
{
public static final int MILLISECONDS_IN_DAY = 86400000;
public static final int MILLISECONDS_IN_HOUR = 3600000;
public static final int MILLISECONDS_IN_MINUTE = 60000;
public static final int MILLISECONDS_IN_SECOND = 1000;
public static final int SECONDS_IN_MINUTE = 60;
public static final int MINUTES_IN_HOUR = 60;
public static final int HOURS_IN_DAY = 24;
private long totalMilliseconds;
private int milliseconds;
private int seconds;
private int minutes;
private int hours;
private int days;
public TimeSpan(long milliseconds)
{
this.totalMilliseconds = milliseconds;
this.milliseconds = (int) (milliseconds % MILLISECONDS_IN_SECOND);
milliseconds /= MILLISECONDS_IN_SECOND;
this.seconds = (int) (milliseconds % SECONDS_IN_MINUTE);
milliseconds /= SECONDS_IN_MINUTE;
this.minutes = (int) (milliseconds % MINUTES_IN_HOUR);
milliseconds /= MINUTES_IN_HOUR;
this.hours = (int) (milliseconds % HOURS_IN_DAY);
milliseconds /= HOURS_IN_DAY;
this.days = (int) milliseconds;
}
public static TimeSpan create(int minutes, int seconds)
{
long totalMilliseconds = (seconds * MILLISECONDS_IN_SECOND);
totalMilliseconds += (minutes * MILLISECONDS_IN_MINUTE);
return new TimeSpan(totalMilliseconds);
}
public static TimeSpan create(int hours, int minutes, int seconds)
{
long totalMilliseconds = (seconds * MILLISECONDS_IN_SECOND);
totalMilliseconds += (minutes * MILLISECONDS_IN_MINUTE);
totalMilliseconds += (hours * MILLISECONDS_IN_HOUR);
return new TimeSpan(totalMilliseconds);
}
public static TimeSpan create(long days, long hours, long minutes, long seconds)
{
long totalMilliseconds = (seconds * MILLISECONDS_IN_SECOND);
totalMilliseconds += (minutes * MILLISECONDS_IN_MINUTE);
totalMilliseconds += (hours * MILLISECONDS_IN_HOUR);
totalMilliseconds += (days * MILLISECONDS_IN_DAY);
return new TimeSpan(totalMilliseconds);
}
public static TimeSpan create(long days, long hours, long minutes, long seconds, long milliseconds)
{
long totalMilliseconds = milliseconds;
totalMilliseconds += (seconds * MILLISECONDS_IN_SECOND);
totalMilliseconds += (minutes * MILLISECONDS_IN_MINUTE);
totalMilliseconds += (hours * MILLISECONDS_IN_HOUR);
totalMilliseconds += (days * MILLISECONDS_IN_DAY);
return new TimeSpan(totalMilliseconds);
}
public static TimeSpan create(Calendar cal)
{
return new TimeSpan(cal.getTimeInMillis());
}
public static TimeSpan create(Date date)
{
return new TimeSpan(date.getTime());
}
public static TimeSpan getCurrentTime()
{
return new TimeSpan(System.currentTimeMillis());
}
public int getDays()
{
return days;
}
public int getHours()
{
return hours;
}
public int getMinutes()
{
return minutes;
}
public int getSeconds()
{
return seconds;
}
public int getMilliseconds()
{
return milliseconds;
}
public double getTotalDays()
{
return totalMilliseconds / (double) (MILLISECONDS_IN_DAY);
}
public double getTotalHours()
{
return totalMilliseconds / (double) (MILLISECONDS_IN_HOUR);
}
public double getTotalMinutes()
{
return totalMilliseconds / (double) (MILLISECONDS_IN_MINUTE);
}
public double getTotalSeconds()
{
return totalMilliseconds / (double) (MILLISECONDS_IN_SECOND);
}
public long getTotalMilliseconds()
{
return totalMilliseconds;
}
public TimeSpan add(TimeSpan ts)
{
return new TimeSpan(this.totalMilliseconds + ts.totalMilliseconds);
}
public TimeSpan add(long milliseconds)
{
return new TimeSpan(this.totalMilliseconds + milliseconds);
}
public TimeSpan subtract(TimeSpan ts)
{
return new TimeSpan(this.totalMilliseconds - ts.totalMilliseconds);
}
public static int compare(TimeSpan t1, TimeSpan t2)
{
if (t1.totalMilliseconds < t2.totalMilliseconds)
{
return -1;
}
else
{
return t1.totalMilliseconds == t2.totalMilliseconds ? 0 : 1;
}
}
}

View File

@ -1,246 +0,0 @@
package org.moire.ultrasonic.util;
import android.content.Context;
import android.content.res.Resources;
import android.text.Editable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import org.moire.ultrasonic.R;
/**
* Created by Joshua Bahnsen on 12/22/13.
*/
public class TimeSpanPicker extends LinearLayout implements AdapterView.OnItemSelectedListener
{
private EditText timeSpanEditText;
private Spinner timeSpanSpinner;
private CheckBox timeSpanDisableCheckbox;
private TimeSpan timeSpan;
private ArrayAdapter<CharSequence> adapter;
private Context context;
private View dialog;
public TimeSpanPicker(Context context)
{
this(context, null);
this.context = context;
}
public TimeSpanPicker(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
this.context = context;
}
public TimeSpanPicker(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
this.context = context;
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
dialog = inflater.inflate(R.layout.time_span_dialog, this, true);
timeSpan = new TimeSpan(-1);
timeSpanEditText = (EditText) dialog.findViewById(R.id.timeSpanEditText);
timeSpanEditText.setText("0");
timeSpanSpinner = (Spinner) dialog.findViewById(R.id.timeSpanSpinner);
timeSpanDisableCheckbox = (CheckBox) dialog.findViewById(R.id.timeSpanDisableCheckBox);
timeSpanDisableCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
{
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b)
{
timeSpanEditText.setEnabled(!b);
timeSpanSpinner.setEnabled(!b);
}
});
adapter = ArrayAdapter.createFromResource(context, R.array.shareExpirationNames, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
timeSpanSpinner.setAdapter(adapter);
timeSpanSpinner.setOnItemSelectedListener(this);
}
@Override
public void setEnabled(boolean enabled)
{
timeSpanEditText.setEnabled(enabled);
timeSpanSpinner.setEnabled(enabled);
}
public TimeSpan getTimeSpan()
{
this.timeSpan = !timeSpanDisableCheckbox.isChecked() ? getTimeSpanFromDialog(this.context, dialog) : new TimeSpan(0);
return timeSpan;
}
public boolean getTimeSpanEnabled()
{
return !timeSpanDisableCheckbox.isChecked();
}
public String getTimeSpanType()
{
EditText timeSpanEditText = (EditText) dialog.findViewById(R.id.timeSpanEditText);
if (timeSpanEditText == null)
{
return null;
}
return (String) timeSpanSpinner.getSelectedItem();
}
public int getTimeSpanAmount()
{
Spinner timeSpanSpinner = (Spinner) dialog.findViewById(R.id.timeSpanSpinner);
if (timeSpanSpinner == null)
{
return -1;
}
Editable text = timeSpanEditText.getText();
String timeSpanAmountString = null;
if (text != null)
{
timeSpanAmountString = text.toString();
}
int timeSpanAmount = 0;
if (timeSpanAmountString != null && !"".equals(timeSpanAmountString))
{
timeSpanAmount = Integer.parseInt(timeSpanAmountString);
}
return timeSpanAmount;
}
public void setTimeSpanAmount(CharSequence amount)
{
timeSpanEditText.setText(amount);
}
public void setTimeSpanType(CharSequence type)
{
timeSpanSpinner.setSelection(adapter.getPosition(type));
}
public void setTimeSpanDisableText(CharSequence text)
{
timeSpanDisableCheckbox.setText(text);
}
public void setTimeSpanDisableCheckboxChecked(boolean checked)
{
timeSpanDisableCheckbox.setChecked(checked);
}
public static TimeSpan getTimeSpanFromDialog(Context context, View dialog)
{
EditText timeSpanEditText = (EditText) dialog.findViewById(R.id.timeSpanEditText);
Spinner timeSpanSpinner = (Spinner) dialog.findViewById(R.id.timeSpanSpinner);
if (timeSpanEditText == null || timeSpanSpinner == null)
{
return new TimeSpan(-1);
}
String timeSpanType = (String) timeSpanSpinner.getSelectedItem();
Editable text = timeSpanEditText.getText();
String timeSpanAmountString = null;
if (text != null)
{
timeSpanAmountString = text.toString();
}
int timeSpanAmount = 0;
if (timeSpanAmountString != null && !"".equals(timeSpanAmountString))
{
timeSpanAmount = Integer.parseInt(timeSpanAmountString);
}
return calculateTimeSpan(context, timeSpanType, timeSpanAmount);
}
public static TimeSpan calculateTimeSpan(Context context, String timeSpanType, int timeSpanAmount)
{
TimeSpan timeSpan = null;
Resources resources = context.getResources();
if (resources.getText(R.string.settings_share_milliseconds).equals(timeSpanType))
{
timeSpan = new TimeSpan(timeSpanAmount);
}
else if (resources.getText(R.string.settings_share_seconds).equals(timeSpanType))
{
timeSpan = TimeSpan.create(0, timeSpanAmount);
}
else if (resources.getText(R.string.settings_share_minutes).equals(timeSpanType))
{
timeSpan = TimeSpan.create(timeSpanAmount, 0);
}
else if (resources.getText(R.string.settings_share_hours).equals(timeSpanType))
{
timeSpan = TimeSpan.create(timeSpanAmount, 0, 0);
}
else if (resources.getText(R.string.settings_share_days).equals(timeSpanType))
{
timeSpan = TimeSpan.create(timeSpanAmount, 0, 0, 0);
}
return timeSpan;
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id)
{
String timeSpanType = (String) parent.getItemAtPosition(pos);
Editable text = timeSpanEditText.getText();
if (text == null)
{
return;
}
String timeSpanAmountString = text.toString();
int timeSpanAmount = 0;
if (timeSpanAmountString != null && !"".equals(timeSpanAmountString))
{
timeSpanAmount = Integer.parseInt(timeSpanAmountString);
}
this.timeSpan = calculateTimeSpan(this.context, timeSpanType, timeSpanAmount);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView)
{
}
}

View File

@ -0,0 +1,166 @@
/*
* TimeSpanPicker.kt
* Copyright (C) 2009-2022 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.util
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.CheckBox
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.Spinner
import java.util.concurrent.TimeUnit
import org.moire.ultrasonic.R
import timber.log.Timber
/**
* UI Dialog which allow the User to pick a duration ranging from milliseconds to month
*/
class TimeSpanPicker(private var mContext: Context, attrs: AttributeSet?, defStyle: Int) :
LinearLayout(
mContext, attrs, defStyle
),
AdapterView.OnItemSelectedListener {
private val timeSpanEditText: EditText
private val timeSpanSpinner: Spinner
private val timeSpanDisableCheckbox: CheckBox
private var mTimeSpan: Long = -1L
private val adapter: ArrayAdapter<CharSequence>
private val dialog: View
constructor(context: Context) : this(context, null) {
this.mContext = context
}
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) {
this.mContext = context
}
init {
val inflater = mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
dialog = inflater.inflate(R.layout.time_span_dialog, this, true)
timeSpanEditText = dialog.findViewById<View>(R.id.timeSpanEditText) as EditText
timeSpanEditText.setText("0")
timeSpanSpinner = dialog.findViewById<View>(R.id.timeSpanSpinner) as Spinner
timeSpanDisableCheckbox =
dialog.findViewById<View>(R.id.timeSpanDisableCheckBox) as CheckBox
timeSpanDisableCheckbox.setOnCheckedChangeListener { _, b ->
timeSpanEditText.isEnabled = !b
timeSpanSpinner.isEnabled = !b
}
adapter = ArrayAdapter.createFromResource(
mContext,
R.array.shareExpirationNames,
android.R.layout.simple_spinner_item
)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
timeSpanSpinner.adapter = adapter
timeSpanSpinner.onItemSelectedListener = this
}
override fun setEnabled(enabled: Boolean) {
timeSpanEditText.isEnabled = enabled
timeSpanSpinner.isEnabled = enabled
}
fun getTimeSpan(): Long {
return if (!timeSpanDisableCheckbox.isChecked) getTimeSpanFromDialog(
mContext, dialog
) else -1L
}
val timeSpanEnabled: Boolean
get() = !timeSpanDisableCheckbox.isChecked
var timeSpanType: String?
get() {
return timeSpanSpinner.selectedItem as String
}
set(type) {
timeSpanSpinner.setSelection(adapter.getPosition(type))
}
val timeSpanAmount: Int
get() {
val text = timeSpanEditText.text
var timeSpanAmountString: String? = null
if (text != null) {
timeSpanAmountString = text.toString()
}
var timeSpanAmount = 0
if (timeSpanAmountString != null && "" != timeSpanAmountString) {
timeSpanAmount = timeSpanAmountString.toInt()
}
return timeSpanAmount
}
fun setTimeSpanAmount(amount: CharSequence?) {
timeSpanEditText.setText(amount)
}
fun setTimeSpanDisableText(text: CharSequence?) {
timeSpanDisableCheckbox.text = text
}
fun setTimeSpanDisableCheckboxChecked(checked: Boolean) {
timeSpanDisableCheckbox.isChecked = checked
}
override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) {
val timeSpanType = parent.getItemAtPosition(pos) as String
val text = timeSpanEditText.text ?: return
val timeSpanAmountString = text.toString()
var timeSpanAmount = 0L
if ("" != timeSpanAmountString) {
timeSpanAmount = timeSpanAmountString.toLong()
}
mTimeSpan = calculateTimeSpan(mContext, timeSpanType, timeSpanAmount)
}
override fun onNothingSelected(adapterView: AdapterView<*>?) {}
companion object {
fun getTimeSpanFromDialog(context: Context, dialog: View): Long {
val timeSpanEditText = dialog.findViewById<View>(R.id.timeSpanEditText) as EditText
val timeSpanSpinner = dialog.findViewById<View>(R.id.timeSpanSpinner) as Spinner
val timeSpanType = timeSpanSpinner.selectedItem as String
Timber.i("SELECTED ITEM: %d", timeSpanSpinner.selectedItemId)
val text = timeSpanEditText.text
val timeSpanAmountString: String? = text?.toString()
var timeSpanAmount = 0L
if (timeSpanAmountString != null && timeSpanAmountString != "") {
timeSpanAmount = timeSpanAmountString.toLong()
}
return calculateTimeSpan(context, timeSpanType, timeSpanAmount)
}
fun calculateTimeSpan(
context: Context,
timeSpanType: String,
timeSpanAmount: Long
): Long {
val resources = context.resources
return when (timeSpanType) {
resources.getText(R.string.settings_share_minutes) -> {
TimeUnit.MINUTES.toMillis(timeSpanAmount)
}
resources.getText(R.string.settings_share_hours) -> {
TimeUnit.HOURS.toMillis(timeSpanAmount)
}
resources.getText(R.string.settings_share_days) -> {
TimeUnit.DAYS.toMillis(timeSpanAmount)
}
else -> TimeUnit.MINUTES.toMillis(0L)
}
}
}
}

View File

@ -1,86 +0,0 @@
package org.moire.ultrasonic.util;
import android.content.Context;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.DialogPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceDialogFragmentCompat;
import org.moire.ultrasonic.R;
import java.util.Locale;
import java.util.regex.Pattern;
/**
* Created by Joshua Bahnsen on 12/22/13.
*/
public class TimeSpanPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat implements DialogPreference.TargetFragment
{
private static final Pattern COMPILE = Pattern.compile(":");
Context context;
TimeSpanPicker picker;
@Override
protected View onCreateDialogView(Context context) {
picker = new TimeSpanPicker(context);
this.context = context;
picker.setTimeSpanDisableText(this.context.getResources().getString(R.string.no_expiration));
Preference preference = getPreference();
String persisted = preference.getSharedPreferences().getString(preference.getKey(), "");
if (!"".equals(persisted))
{
String[] split = COMPILE.split(persisted);
if (split.length == 2)
{
String amount = split[0];
if ("0".equals(amount) || "".equals(amount))
{
picker.setTimeSpanDisableCheckboxChecked(true);
}
picker.setTimeSpanAmount(amount);
picker.setTimeSpanType(split[1]);
}
}
else
{
picker.setTimeSpanDisableCheckboxChecked(true);
}
return picker;
}
@Override
public void onDialogClosed(boolean positiveResult)
{
String persisted = "";
if (picker.getTimeSpanEnabled())
{
int tsAmount = picker.getTimeSpanAmount();
if (tsAmount > 0)
{
String tsType = picker.getTimeSpanType();
persisted = String.format(Locale.US, "%d:%s", tsAmount, tsType);
}
}
Preference preference = getPreference();
preference.getSharedPreferences().edit().putString(preference.getKey(), persisted).apply();
}
@Nullable
@Override
public Preference findPreference(@NonNull CharSequence key) {
return getPreference();
}
}

View File

@ -0,0 +1,58 @@
/*
* TimeSpanPreferenceDialogFragmentCompat.kt
* Copyright (C) 2009-2022 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.util
import android.content.Context
import android.view.View
import androidx.preference.PreferenceDialogFragmentCompat
import androidx.preference.DialogPreference.TargetFragment
import androidx.preference.Preference
import org.moire.ultrasonic.R
import java.util.Locale
class TimeSpanPreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat(), TargetFragment {
var picker: TimeSpanPicker? = null
override fun onCreateDialogView(context: Context): View? {
picker = TimeSpanPicker(context)
picker!!.setTimeSpanDisableText(requireContext().resources.getString(R.string.no_expiration))
val persisted = Settings.defaultShareExpiration
if ("" != persisted) {
val split = Settings.COLON_PATTERN.split(persisted)
if (split.size == 2) {
val amount = split[0]
if ("0" == amount || "" == amount) {
picker!!.setTimeSpanDisableCheckboxChecked(true)
}
picker!!.setTimeSpanAmount(amount)
picker!!.timeSpanType = split[1]
}
} else {
picker!!.setTimeSpanDisableCheckboxChecked(true)
}
return picker
}
override fun onDialogClosed(positiveResult: Boolean) {
var persisted = ""
if (picker!!.timeSpanEnabled) {
val tsAmount = picker!!.timeSpanAmount
if (tsAmount > 0) {
val tsType = picker!!.timeSpanType
persisted = String.format(Locale.US, "%d:%s", tsAmount, tsType)
}
}
val preference: Preference = preference
preference.sharedPreferences!!.edit().putString(preference.key, persisted).apply()
}
@Suppress("UNCHECKED_CAST")
override fun <T : Preference?> findPreference(p0: CharSequence): T {
return preference as T
}
}

View File

@ -192,6 +192,9 @@ class NavigationActivity : AppCompatActivity() {
showWelcomeDialog() showWelcomeDialog()
} }
// Ask for permission to send notifications
Util.ensurePermissionToPostNotification(this)
RxBus.dismissNowPlayingCommandObservable.subscribe { RxBus.dismissNowPlayingCommandObservable.subscribe {
nowPlayingHidden = true nowPlayingHidden = true
hideNowPlaying() hideNowPlaying()

View File

@ -28,6 +28,7 @@ import org.moire.ultrasonic.R
import org.moire.ultrasonic.audiofx.EqualizerController import org.moire.ultrasonic.audiofx.EqualizerController
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.util.Util.applyTheme import org.moire.ultrasonic.util.Util.applyTheme
import org.w3c.dom.Text
import timber.log.Timber import timber.log.Timber
/** /**
@ -168,10 +169,9 @@ class EqualizerFragment : Fragment() {
val bandBar = LayoutInflater.from(context) val bandBar = LayoutInflater.from(context)
.inflate(R.layout.equalizer_bar, equalizerLayout, false) .inflate(R.layout.equalizer_bar, equalizerLayout, false)
val freqTextView: TextView = val freqTextView: TextView = bandBar.findViewById(R.id.equalizer_frequency)
bandBar.findViewById<View>(R.id.equalizer_frequency) as TextView val levelTextView: TextView = bandBar.findViewById(R.id.equalizer_level)
val levelTextView = bandBar.findViewById<View>(R.id.equalizer_level) as TextView val bar: SeekBar = bandBar.findViewById(R.id.equalizer_bar)
val bar = bandBar.findViewById<View>(R.id.equalizer_bar) as SeekBar
val range = equalizer!!.getBandFreqRange(band) val range = equalizer!!.getBandFreqRange(band)

View File

@ -247,18 +247,15 @@ class SettingsFragment :
} }
override fun onDisplayPreferenceDialog(preference: Preference) { override fun onDisplayPreferenceDialog(preference: Preference) {
var dialogFragment: DialogFragment? = null
if (preference is TimeSpanPreference) { if (preference is TimeSpanPreference) {
dialogFragment = TimeSpanPreferenceDialogFragmentCompat() val dialogFragment = TimeSpanPreferenceDialogFragmentCompat()
val bundle = Bundle(1) val bundle = Bundle(1)
bundle.putString("key", preference.getKey()) bundle.putString("key", preference.getKey())
dialogFragment.setArguments(bundle) dialogFragment.setArguments(bundle)
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0) dialogFragment.setTargetFragment(this, 0)
dialogFragment.show( dialogFragment.show(
this.parentFragmentManager, this.parentFragmentManager,
"android.support.v7.preference.PreferenceFragment.DIALOG" "androidx.preference.PreferenceFragment.DIALOG"
) )
} else { } else {
super.onDisplayPreferenceDialog(preference) super.onDisplayPreferenceDialog(preference)

View File

@ -40,7 +40,6 @@ import org.moire.ultrasonic.util.BackgroundTask
import org.moire.ultrasonic.util.CancellationToken import org.moire.ultrasonic.util.CancellationToken
import org.moire.ultrasonic.util.FragmentBackgroundTask import org.moire.ultrasonic.util.FragmentBackgroundTask
import org.moire.ultrasonic.util.LoadingTask import org.moire.ultrasonic.util.LoadingTask
import org.moire.ultrasonic.util.TimeSpan
import org.moire.ultrasonic.util.TimeSpanPicker import org.moire.ultrasonic.util.TimeSpanPicker
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.view.ShareAdapter import org.moire.ultrasonic.view.ShareAdapter
@ -79,16 +78,16 @@ class SharesFragment : Fragment() {
sharesListView = view.findViewById(R.id.select_share_list) sharesListView = view.findViewById(R.id.select_share_list)
refreshSharesListView!!.setOnRefreshListener { load(true) } refreshSharesListView!!.setOnRefreshListener { load(true) }
emptyTextView = view.findViewById(R.id.select_share_empty) emptyTextView = view.findViewById(R.id.select_share_empty)
sharesListView!!.onItemClickListener = AdapterView.OnItemClickListener { sharesListView!!.onItemClickListener =
parent, _, position, _ -> AdapterView.OnItemClickListener { parent, _, position, _ ->
val share = parent.getItemAtPosition(position) as Share val share = parent.getItemAtPosition(position) as Share
val action = SharesFragmentDirections.sharesToTrackCollection( val action = SharesFragmentDirections.sharesToTrackCollection(
shareId = share.id, shareId = share.id,
shareName = share.name shareName = share.name
) )
findNavController().navigate(action) findNavController().navigate(action)
} }
registerForContextMenu(sharesListView!!) registerForContextMenu(sharesListView!!)
FragmentTitle.setTitle(this, R.string.button_bar_shares) FragmentTitle.setTitle(this, R.string.button_bar_shares)
load(false) load(false)
@ -273,19 +272,19 @@ class SharesFragment : Fragment() {
Entry Count: ${share.getEntries().size} Entry Count: ${share.getEntries().size}
Visit Count: ${share.visitCount} Visit Count: ${share.visitCount}
""".trimIndent() + """.trimIndent() +
( (
if (share.created == null) "" else """ if (share.created == null) "" else """
Creation Date: ${share.created!!.replace('T', ' ')} Creation Date: ${share.created!!.replace('T', ' ')}
""".trimIndent() """.trimIndent()
) + ) +
( (
if (share.lastVisited == null) "" else """ if (share.lastVisited == null) "" else """
Last Visited Date: ${share.lastVisited!!.replace('T', ' ')} Last Visited Date: ${share.lastVisited!!.replace('T', ' ')}
""".trimIndent() """.trimIndent()
) + ) +
if (share.expires == null) "" else """ if (share.expires == null) "" else """
Expiration Date: ${share.expires!!.replace('T', ' ')} Expiration Date: ${share.expires!!.replace('T', ' ')}
""".trimIndent() """.trimIndent()
@ -321,9 +320,9 @@ class SharesFragment : Fragment() {
object : LoadingTask<Any?>(activity, refreshSharesListView, cancellationToken) { object : LoadingTask<Any?>(activity, refreshSharesListView, cancellationToken) {
@Throws(Throwable::class) @Throws(Throwable::class)
override fun doInBackground(): Any? { override fun doInBackground(): Any? {
var millis = timeSpanPicker.timeSpan.totalMilliseconds var millis = timeSpanPicker.getTimeSpan()
if (millis > 0) { if (millis > 0) {
millis = TimeSpan.getCurrentTime().add(millis).totalMilliseconds millis += System.currentTimeMillis()
} }
val shareDescriptionText = shareDescription.text val shareDescriptionText = shareDescription.text
val description = shareDescriptionText?.toString() val description = shareDescriptionText?.toString()
@ -341,19 +340,22 @@ class SharesFragment : Fragment() {
} }
override fun error(error: Throwable) { override fun error(error: Throwable) {
val msg: String val msg: String =
msg = if (error is OfflineException || error is ApiNotSupportedException) { if (error is OfflineException || error is ApiNotSupportedException) {
getErrorMessage( getErrorMessage(
error error
) )
} else { } else {
String.format( String.format(
Locale.ROOT, Locale.ROOT,
"%s %s", "%s %s",
resources.getString(R.string.playlist_updated_info_error, share.name), resources.getString(
getErrorMessage(error) R.string.playlist_updated_info_error,
) share.name
} ),
getErrorMessage(error)
)
}
Util.toast(context, msg, false) Util.toast(context, msg, false)
} }
}.execute() }.execute()

View File

@ -7,9 +7,11 @@
package org.moire.ultrasonic.imageloader package org.moire.ultrasonic.imageloader
import android.annotation.SuppressLint
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.BitmapLoader import androidx.media3.session.BitmapLoader
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.ListeningExecutorService import com.google.common.util.concurrent.ListeningExecutorService
@ -19,6 +21,7 @@ import java.util.concurrent.Executors
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
@SuppressLint("UnsafeOptInUsageError")
class ArtworkBitmapLoader : BitmapLoader, KoinComponent { class ArtworkBitmapLoader : BitmapLoader, KoinComponent {
private val imageLoader: ImageLoader by inject() private val imageLoader: ImageLoader by inject()

View File

@ -7,11 +7,11 @@
package org.moire.ultrasonic.playback package org.moire.ultrasonic.playback
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.media3.common.HeartRating import androidx.media3.common.HeartRating
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.CommandButton import androidx.media3.session.CommandButton
import androidx.media3.session.DefaultMediaNotificationProvider import androidx.media3.session.DefaultMediaNotificationProvider
import androidx.media3.session.MediaNotification import androidx.media3.session.MediaNotification
@ -24,7 +24,7 @@ import org.moire.ultrasonic.imageloader.ArtworkBitmapLoader
import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.util.toTrack import org.moire.ultrasonic.util.toTrack
@UnstableApi @SuppressLint("UnsafeOptInUsageError")
class MediaNotificationProvider(context: Context) : class MediaNotificationProvider(context: Context) :
DefaultMediaNotificationProvider(context, ArtworkBitmapLoader()), KoinComponent { DefaultMediaNotificationProvider(context, ArtworkBitmapLoader()), KoinComponent {

View File

@ -6,6 +6,7 @@
*/ */
package org.moire.ultrasonic.playback package org.moire.ultrasonic.playback
import android.annotation.SuppressLint
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
@ -39,6 +40,7 @@ import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.util.toTrack import org.moire.ultrasonic.util.toTrack
import timber.log.Timber import timber.log.Timber
@SuppressLint("UnsafeOptInUsageError")
class PlaybackService : MediaLibraryService(), KoinComponent { class PlaybackService : MediaLibraryService(), KoinComponent {
private lateinit var player: ExoPlayer private lateinit var player: ExoPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession private lateinit var mediaLibrarySession: MediaLibrarySession
@ -84,7 +86,12 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
mediaLibrarySession.release() mediaLibrarySession.release()
rxBusSubscription.dispose() rxBusSubscription.dispose()
isStarted = false isStarted = false
stopForeground(true) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_REMOVE)
} else {
@Suppress("DEPRECATION")
stopForeground(true)
}
stopSelf() stopSelf()
} }

View File

@ -39,7 +39,6 @@ import androidx.media3.common.Timeline
import androidx.media3.common.TrackSelectionParameters import androidx.media3.common.TrackSelectionParameters
import androidx.media3.common.VideoSize import androidx.media3.common.VideoSize
import androidx.media3.common.text.CueGroup import androidx.media3.common.text.CueGroup
import androidx.media3.common.util.Util
import androidx.media3.session.MediaSession import androidx.media3.session.MediaSession
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
@ -76,6 +75,7 @@ private const val SEEK_START_AFTER_SECONDS = 5
* TODO: Minimize status updates. * TODO: Minimize status updates.
*/ */
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
@SuppressLint("UnsafeOptInUsageError")
class JukeboxMediaPlayer : JukeboxUnimplementedFunctions(), Player { class JukeboxMediaPlayer : JukeboxUnimplementedFunctions(), Player {
private val tasks = TaskQueue() private val tasks = TaskQueue()
private val executorService = Executors.newSingleThreadScheduledExecutor() private val executorService = Executors.newSingleThreadScheduledExecutor()
@ -119,7 +119,7 @@ class JukeboxMediaPlayer : JukeboxUnimplementedFunctions(), Player {
JukeboxNotificationActionFactory() JukeboxNotificationActionFactory()
) {} ) {}
if (Util.SDK_INT >= 29) { if (Build.VERSION.SDK_INT >= 29) {
startForeground( startForeground(
notification.notificationId, notification.notificationId,
notification.notification, notification.notification,
@ -140,7 +140,12 @@ class JukeboxMediaPlayer : JukeboxUnimplementedFunctions(), Player {
val extras = intent.extras val extras = intent.extras
if ((extras != null) && extras.containsKey(Intent.EXTRA_KEY_EVENT)) { if ((extras != null) && extras.containsKey(Intent.EXTRA_KEY_EVENT)) {
val event = extras.getParcelable<KeyEvent>(Intent.EXTRA_KEY_EVENT) val event = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
extras.getParcelable(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java)
} else {
@Suppress("DEPRECATION")
extras.getParcelable<KeyEvent>(Intent.EXTRA_KEY_EVENT)
}
when (event?.keyCode) { when (event?.keyCode) {
KEYCODE_MEDIA_PLAY -> play() KEYCODE_MEDIA_PLAY -> play()
KEYCODE_MEDIA_PAUSE -> stop() KEYCODE_MEDIA_PAUSE -> stop()

View File

@ -7,6 +7,7 @@
package org.moire.ultrasonic.service package org.moire.ultrasonic.service
import android.annotation.SuppressLint
import android.app.PendingIntent import android.app.PendingIntent
import android.content.ComponentName import android.content.ComponentName
import android.content.Intent import android.content.Intent
@ -25,6 +26,7 @@ import org.moire.ultrasonic.app.UApp
* This class creates Intents and Actions to be used with the Media Notification * This class creates Intents and Actions to be used with the Media Notification
* of the Jukebox Service * of the Jukebox Service
*/ */
@SuppressLint("UnsafeOptInUsageError")
class JukeboxNotificationActionFactory : MediaNotification.ActionFactory { class JukeboxNotificationActionFactory : MediaNotification.ActionFactory {
override fun createMediaAction( override fun createMediaAction(
mediaSession: MediaSession, mediaSession: MediaSession,

View File

@ -1,5 +1,5 @@
/* /*
* JukeboxUnimplemented.kt * JukeboxUnimplementedFunctions.kt
* Copyright (C) 2009-2022 Ultrasonic developers * Copyright (C) 2009-2022 Ultrasonic developers
* *
* Distributed under terms of the GNU GPLv3 license. * Distributed under terms of the GNU GPLv3 license.
@ -7,6 +7,7 @@
package org.moire.ultrasonic.service package org.moire.ultrasonic.service
import android.annotation.SuppressLint
import android.app.Service import android.app.Service
import android.view.Surface import android.view.Surface
import android.view.SurfaceHolder import android.view.SurfaceHolder
@ -24,6 +25,7 @@ import androidx.media3.common.Tracks
* of the crowded Player interface, so the JukeboxMediaPlayer class can be a bit clearer. * of the crowded Player interface, so the JukeboxMediaPlayer class can be a bit clearer.
*/ */
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
@SuppressLint("UnsafeOptInUsageError")
abstract class JukeboxUnimplementedFunctions : Service(), Player { abstract class JukeboxUnimplementedFunctions : Service(), Player {
override fun setMediaItems(mediaItems: MutableList<MediaItem>) { override fun setMediaItems(mediaItems: MutableList<MediaItem>) {

View File

@ -7,6 +7,7 @@
package org.moire.ultrasonic.service package org.moire.ultrasonic.service
import android.annotation.SuppressLint
import androidx.media3.common.C import androidx.media3.common.C
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.Player import androidx.media3.common.Player
@ -20,6 +21,7 @@ import java.util.Arrays
* This class wraps a simple playlist provided as List<MediaItem> * This class wraps a simple playlist provided as List<MediaItem>
* to be usable as a Media3 Timeline. * to be usable as a Media3 Timeline.
*/ */
@SuppressLint("UnsafeOptInUsageError")
class PlaylistTimeline @JvmOverloads constructor( class PlaylistTimeline @JvmOverloads constructor(
mediaItems: List<MediaItem>, mediaItems: List<MediaItem>,
shuffledIndices: IntArray = createUnshuffledIndices( shuffledIndices: IntArray = createUnshuffledIndices(

View File

@ -30,7 +30,6 @@ import org.moire.ultrasonic.util.CancellationToken
import org.moire.ultrasonic.util.FragmentBackgroundTask import org.moire.ultrasonic.util.FragmentBackgroundTask
import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.ShareDetails import org.moire.ultrasonic.util.ShareDetails
import org.moire.ultrasonic.util.TimeSpan
import org.moire.ultrasonic.util.TimeSpanPicker import org.moire.ultrasonic.util.TimeSpanPicker
import org.moire.ultrasonic.util.Util.ifNotNull import org.moire.ultrasonic.util.Util.ifNotNull
@ -145,9 +144,8 @@ class ShareHandler(val context: Context) {
showDialog(fragment, shareDetails, swipe, cancellationToken, additionalId) showDialog(fragment, shareDetails, swipe, cancellationToken, additionalId)
} else { } else {
shareDetails.Description = Settings.defaultShareDescription shareDetails.Description = Settings.defaultShareDescription
shareDetails.Expiration = TimeSpan.getCurrentTime().add( shareDetails.Expiration = System.currentTimeMillis() +
Settings.defaultShareExpirationInMillis Settings.defaultShareExpirationInMillis
).totalMilliseconds
share(fragment, shareDetails, swipe, cancellationToken, additionalId) share(fragment, shareDetails, swipe, cancellationToken, additionalId)
} }
} }
@ -165,11 +163,11 @@ class ShareHandler(val context: Context) {
shareDescription = layout.findViewById<View>(R.id.share_description) as EditText shareDescription = layout.findViewById<View>(R.id.share_description) as EditText
hideDialogCheckBox = layout.findViewById<View>(R.id.hide_dialog) as CheckBox hideDialogCheckBox = layout.findViewById<View>(R.id.hide_dialog) as CheckBox
shareOnServerCheckBox = layout.findViewById<View>(R.id.share_on_server) as CheckBox shareOnServerCheckBox = layout.findViewById<View>(R.id.share_on_server) as CheckBox
noExpirationCheckBox = layout.findViewById<View>(
R.id.timeSpanDisableCheckBox
) as CheckBox
saveAsDefaultsCheckBox = layout.findViewById<View>(R.id.save_as_defaults) as CheckBox saveAsDefaultsCheckBox = layout.findViewById<View>(R.id.save_as_defaults) as CheckBox
timeSpanPicker = layout.findViewById<View>(R.id.date_picker) as TimeSpanPicker timeSpanPicker = layout.findViewById<View>(R.id.date_picker) as TimeSpanPicker
noExpirationCheckBox = timeSpanPicker!!.findViewById<View>(
R.id.timeSpanDisableCheckBox
) as CheckBox
textViewComment = layout.findViewById<View>(R.id.textViewComment) as TextView textViewComment = layout.findViewById<View>(R.id.textViewComment) as TextView
textViewExpiration = layout.findViewById<View>(R.id.textViewExpiration) as TextView textViewExpiration = layout.findViewById<View>(R.id.textViewExpiration) as TextView
} }
@ -191,9 +189,8 @@ class ShareHandler(val context: Context) {
builder.setPositiveButton(R.string.menu_share) { _, _ -> builder.setPositiveButton(R.string.menu_share) { _, _ ->
if (!noExpirationCheckBox!!.isChecked) { if (!noExpirationCheckBox!!.isChecked) {
val timeSpan: TimeSpan = timeSpanPicker!!.timeSpan val timeSpan: Long = timeSpanPicker!!.getTimeSpan()
val now = TimeSpan.getCurrentTime() shareDetails.Expiration = System.currentTimeMillis() + timeSpan
shareDetails.Expiration = now.add(timeSpan).totalMilliseconds
} }
shareDetails.Description = shareDescription!!.text.toString() shareDetails.Description = shareDescription!!.text.toString()
@ -204,7 +201,7 @@ class ShareHandler(val context: Context) {
} }
if (saveAsDefaultsCheckBox!!.isChecked) { if (saveAsDefaultsCheckBox!!.isChecked) {
val timeSpanType: String = timeSpanPicker!!.timeSpanType val timeSpanType: String = timeSpanPicker!!.timeSpanType!!
val timeSpanAmount: Int = timeSpanPicker!!.timeSpanAmount val timeSpanAmount: Int = timeSpanPicker!!.timeSpanAmount
Settings.defaultShareExpiration = Settings.defaultShareExpiration =
if (!noExpirationCheckBox!!.isChecked && timeSpanAmount > 0) if (!noExpirationCheckBox!!.isChecked && timeSpanAmount > 0)
@ -240,7 +237,7 @@ class ShareHandler(val context: Context) {
noExpirationCheckBox!!.isChecked = false noExpirationCheckBox!!.isChecked = false
timeSpanPicker!!.isEnabled = true timeSpanPicker!!.isEnabled = true
timeSpanPicker!!.setTimeSpanAmount(timeSpanAmount.toString()) timeSpanPicker!!.setTimeSpanAmount(timeSpanAmount.toString())
timeSpanPicker!!.setTimeSpanType(timeSpanType) timeSpanPicker!!.timeSpanType = timeSpanType
} else { } else {
noExpirationCheckBox!!.isChecked = true noExpirationCheckBox!!.isChecked = true
timeSpanPicker!!.isEnabled = false timeSpanPicker!!.isEnabled = false

View File

@ -18,7 +18,7 @@ import org.moire.ultrasonic.app.UApp
* Contains convenience functions for reading and writing preferences * Contains convenience functions for reading and writing preferences
*/ */
object Settings { object Settings {
private val PATTERN = Pattern.compile(":")
@JvmStatic @JvmStatic
var theme by StringSetting( var theme by StringSetting(
@ -229,15 +229,12 @@ object Settings {
val defaultShareExpirationInMillis: Long val defaultShareExpirationInMillis: Long
get() { get() {
val preference = val preference = defaultShareExpiration
preferences.getString(getKey(R.string.setting_key_default_share_expiration), "0")!! val split = COLON_PATTERN.split(preference)
val split = PATTERN.split(preference)
if (split.size == 2) { if (split.size == 2) {
val timeSpanAmount = split[0].toInt() val timeSpanAmount = split[0].toLong()
val timeSpanType = split[1] val timeSpanType = split[1]
val timeSpan = return TimeSpanPicker.calculateTimeSpan(appContext, timeSpanType, timeSpanAmount)
TimeSpanPicker.calculateTimeSpan(appContext, timeSpanType, timeSpanAmount)
return timeSpan.totalMilliseconds
} }
return 0 return 0
} }
@ -292,4 +289,6 @@ object Settings {
private val appContext: Context private val appContext: Context
get() = UApp.applicationContext() get() = UApp.applicationContext()
val COLON_PATTERN = Pattern.compile(":")
} }

View File

@ -1,6 +1,6 @@
/* /*
* StorageFile.kt * StorageFile.kt
* Copyright (C) 2009-2021 Ultrasonic developers * Copyright (C) 2009-2022 Ultrasonic developers
* *
* Distributed under terms of the GNU GPLv3 license. * Distributed under terms of the GNU GPLv3 license.
*/ */
@ -91,8 +91,10 @@ class StorageFile(
uri, uri,
mode mode
) )
return descriptor?.createOutputStream() val stream = descriptor?.createOutputStream()
?: throw IOException("Couldn't retrieve OutputStream") ?: throw IOException("Couldn't retrieve OutputStream")
descriptor.close()
return stream
} }
override fun getFileInputStream(): InputStream { override fun getFileInputStream(): InputStream {

View File

@ -7,6 +7,7 @@
package org.moire.ultrasonic.util package org.moire.ultrasonic.util
import android.Manifest.permission.POST_NOTIFICATIONS
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.PendingIntent import android.app.PendingIntent
@ -32,7 +33,10 @@ import android.util.TypedValue
import android.view.Gravity import android.view.Gravity
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.AnyRes import androidx.annotation.AnyRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.media3.common.C import androidx.media3.common.C
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.Player import androidx.media3.common.Player
@ -508,6 +512,25 @@ object Util {
} }
} }
fun ensurePermissionToPostNotification(fragment: AppCompatActivity) {
if (ContextCompat.checkSelfPermission(
applicationContext(),
POST_NOTIFICATIONS,
) != PackageManager.PERMISSION_GRANTED &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
) {
val requestPermissionLauncher =
fragment.registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (!it) {
toast(applicationContext(), R.string.notification_permission_required)
}
}
requestPermissionLauncher.launch(POST_NOTIFICATIONS)
}
}
@JvmStatic @JvmStatic
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
fun getVersionName(context: Context): String? { fun getVersionName(context: Context): String? {

View File

@ -1,11 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
~ equalizer_bar.xml
~ Copyright (C) 2009-2022 Ultrasonic developers
~
~ Distributed under terms of the GNU GPLv3 license.
-->
<RelativeLayout xmlns:a="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:a="http://schemas.android.com/apk/res/android"
a:orientation="vertical" a:orientation="vertical"
a:layout_width="fill_parent" a:layout_width="fill_parent"
a:layout_height="wrap_content"> a:layout_height="wrap_content">
<TextView <TextView
a:id="@+id/equalizer.frequency" a:id="@+id/equalizer_frequency"
a:textSize="12sp" a:textSize="12sp"
a:textColor="#c0c0c0" a:textColor="#c0c0c0"
a:layout_width="wrap_content" a:layout_width="wrap_content"
@ -15,7 +22,7 @@
/> />
<TextView <TextView
a:id="@+id/equalizer.level" a:id="@+id/equalizer_level"
a:text="0 dB" a:text="0 dB"
a:textSize="12sp" a:textSize="12sp"
a:textColor="#c0c0c0" a:textColor="#c0c0c0"
@ -24,14 +31,14 @@
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:layout_marginTop="8dp" a:layout_marginTop="8dp"
a:layout_alignParentRight="true" a:layout_alignParentRight="true"
a:layout_toEndOf="@+id/equalizer.frequency" a:layout_toEndOf="@+id/equalizer_frequency"
/> />
<SeekBar <SeekBar
a:id="@+id/equalizer.bar" a:id="@+id/equalizer_bar"
a:layout_width="fill_parent" a:layout_width="fill_parent"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:layout_below="@+id/equalizer.frequency" a:layout_below="@+id/equalizer_frequency"
/> />

View File

@ -292,8 +292,6 @@
<string name="select_share.empty">Na serveru nejsou žádná sdílení</string> <string name="select_share.empty">Na serveru nejsou žádná sdílení</string>
<string name="menu_deleted_share">Smazaná sdílení %s</string> <string name="menu_deleted_share">Smazaná sdílení %s</string>
<string name="menu_deleted_share_error">Chyba vymazání sdílení %s</string> <string name="menu_deleted_share_error">Chyba vymazání sdílení %s</string>
<string name="settings.share_milliseconds">Milisekund</string>
<string name="settings.share_seconds">Sekund</string>
<string name="settings.share_minutes">Minut</string> <string name="settings.share_minutes">Minut</string>
<string name="settings.share_hours">Hodin</string> <string name="settings.share_hours">Hodin</string>
<string name="settings.share_days">Dní</string> <string name="settings.share_days">Dní</string>

View File

@ -351,8 +351,6 @@
<string name="select_share.empty">Keine Freigaben auf dem Server verfügbar</string> <string name="select_share.empty">Keine Freigaben auf dem Server verfügbar</string>
<string name="menu_deleted_share">Freigabe %s gelöscht</string> <string name="menu_deleted_share">Freigabe %s gelöscht</string>
<string name="menu_deleted_share_error">Löschen der Freigabe %s fehlgeschlagen</string> <string name="menu_deleted_share_error">Löschen der Freigabe %s fehlgeschlagen</string>
<string name="settings.share_milliseconds">Millisekunden</string>
<string name="settings.share_seconds">Sekunden</string>
<string name="settings.share_minutes">Minuten</string> <string name="settings.share_minutes">Minuten</string>
<string name="settings.share_hours">Stunden</string> <string name="settings.share_hours">Stunden</string>
<string name="settings.share_days">Tage</string> <string name="settings.share_days">Tage</string>

View File

@ -351,8 +351,6 @@
<string name="select_share.empty">No hay compartidos disponibles en el servidor</string> <string name="select_share.empty">No hay compartidos disponibles en el servidor</string>
<string name="menu_deleted_share">Eliminar compartido %s</string> <string name="menu_deleted_share">Eliminar compartido %s</string>
<string name="menu_deleted_share_error">Fallo al eliminar compartido %s</string> <string name="menu_deleted_share_error">Fallo al eliminar compartido %s</string>
<string name="settings.share_milliseconds">Milisegundos</string>
<string name="settings.share_seconds">segundos</string>
<string name="settings.share_minutes">Minutos</string> <string name="settings.share_minutes">Minutos</string>
<string name="settings.share_hours">Horas</string> <string name="settings.share_hours">Horas</string>
<string name="settings.share_days">Días</string> <string name="settings.share_days">Días</string>

View File

@ -334,8 +334,6 @@
<string name="select_share.empty">Pas de partages disponibles sur le serveur</string> <string name="select_share.empty">Pas de partages disponibles sur le serveur</string>
<string name="menu_deleted_share">Partage supprimé %s</string> <string name="menu_deleted_share">Partage supprimé %s</string>
<string name="menu_deleted_share_error">Échec de la suppression du partage %s</string> <string name="menu_deleted_share_error">Échec de la suppression du partage %s</string>
<string name="settings.share_milliseconds">Millisecondes</string>
<string name="settings.share_seconds">Secondes</string>
<string name="settings.share_minutes">Minutes</string> <string name="settings.share_minutes">Minutes</string>
<string name="settings.share_hours">Heures</string> <string name="settings.share_hours">Heures</string>
<string name="settings.share_days">Jours</string> <string name="settings.share_days">Jours</string>

View File

@ -299,8 +299,6 @@
<string name="select_share.empty">Nincs mentett megosztás a kiszolgálón.</string> <string name="select_share.empty">Nincs mentett megosztás a kiszolgálón.</string>
<string name="menu_deleted_share">%s megosztás törölve</string> <string name="menu_deleted_share">%s megosztás törölve</string>
<string name="menu_deleted_share_error">%s megosztás törlése sikertelen!</string> <string name="menu_deleted_share_error">%s megosztás törlése sikertelen!</string>
<string name="settings.share_milliseconds">Ezredmásodperc</string>
<string name="settings.share_seconds">Másodperc</string>
<string name="settings.share_minutes">Perc</string> <string name="settings.share_minutes">Perc</string>
<string name="settings.share_hours">Óra</string> <string name="settings.share_hours">Óra</string>
<string name="settings.share_days">Nap</string> <string name="settings.share_days">Nap</string>

View File

@ -265,8 +265,6 @@
<string name="widget.sdcard_missing">Nessuna scheda SD</string> <string name="widget.sdcard_missing">Nessuna scheda SD</string>
<string name="settings.share_description_default">Descrizione Predefinita di Condivisione</string> <string name="settings.share_description_default">Descrizione Predefinita di Condivisione</string>
<string name="download.menu_clear_playlist">Pulisci Playlist</string> <string name="download.menu_clear_playlist">Pulisci Playlist</string>
<string name="settings.share_milliseconds">Millisecondi</string>
<string name="settings.share_seconds">Secondi</string>
<string name="settings.share_minutes">Minuti</string> <string name="settings.share_minutes">Minuti</string>
<string name="settings.share_hours">Ore</string> <string name="settings.share_hours">Ore</string>
<string name="settings.share_days">Giorni</string> <string name="settings.share_days">Giorni</string>

View File

@ -350,8 +350,6 @@
<string name="select_share.empty">Geen delingen beschikbaar op server</string> <string name="select_share.empty">Geen delingen beschikbaar op server</string>
<string name="menu_deleted_share">Deling %s verwijderd</string> <string name="menu_deleted_share">Deling %s verwijderd</string>
<string name="menu_deleted_share_error">Kan deling %s niet verwijderen</string> <string name="menu_deleted_share_error">Kan deling %s niet verwijderen</string>
<string name="settings.share_milliseconds">Milliseconden </string>
<string name="settings.share_seconds">Seconden</string>
<string name="settings.share_minutes">Minuten</string> <string name="settings.share_minutes">Minuten</string>
<string name="settings.share_hours">Uur</string> <string name="settings.share_hours">Uur</string>
<string name="settings.share_days">Dagen</string> <string name="settings.share_days">Dagen</string>

View File

@ -288,8 +288,6 @@
<string name="select_share.empty">Brak udostępnień na serwerze</string> <string name="select_share.empty">Brak udostępnień na serwerze</string>
<string name="menu_deleted_share">Usunięto udostępnienie %s</string> <string name="menu_deleted_share">Usunięto udostępnienie %s</string>
<string name="menu_deleted_share_error">Nieudane usunięcie udostępnienia %s</string> <string name="menu_deleted_share_error">Nieudane usunięcie udostępnienia %s</string>
<string name="settings.share_milliseconds">Milisekundy</string>
<string name="settings.share_seconds">Sekundy</string>
<string name="settings.share_minutes">Minuty</string> <string name="settings.share_minutes">Minuty</string>
<string name="settings.share_hours">Godziny</string> <string name="settings.share_hours">Godziny</string>
<string name="settings.share_days">Dni</string> <string name="settings.share_days">Dni</string>

View File

@ -341,8 +341,6 @@
<string name="select_share.empty">Nenhum compartilhamento disponível no servidor</string> <string name="select_share.empty">Nenhum compartilhamento disponível no servidor</string>
<string name="menu_deleted_share">Compartilhamento %s excluído</string> <string name="menu_deleted_share">Compartilhamento %s excluído</string>
<string name="menu_deleted_share_error">Falha ao excluir o compartilhamento %s</string> <string name="menu_deleted_share_error">Falha ao excluir o compartilhamento %s</string>
<string name="settings.share_milliseconds">Milisegundos</string>
<string name="settings.share_seconds">Segundos</string>
<string name="settings.share_minutes">Minutos</string> <string name="settings.share_minutes">Minutos</string>
<string name="settings.share_hours">Horas</string> <string name="settings.share_hours">Horas</string>
<string name="settings.share_days">Dias</string> <string name="settings.share_days">Dias</string>

View File

@ -288,8 +288,6 @@
<string name="select_share.empty">Nenhum compartilhamento disponível no servidor</string> <string name="select_share.empty">Nenhum compartilhamento disponível no servidor</string>
<string name="menu_deleted_share">Compartilhamento %s apagado</string> <string name="menu_deleted_share">Compartilhamento %s apagado</string>
<string name="menu_deleted_share_error">Falha ao apagar o compartilhamento %s</string> <string name="menu_deleted_share_error">Falha ao apagar o compartilhamento %s</string>
<string name="settings.share_milliseconds">Milisegundos</string>
<string name="settings.share_seconds">Segundos</string>
<string name="settings.share_minutes">Minutos</string> <string name="settings.share_minutes">Minutos</string>
<string name="settings.share_hours">Horas</string> <string name="settings.share_hours">Horas</string>
<string name="settings.share_days">Dias</string> <string name="settings.share_days">Dias</string>

View File

@ -318,8 +318,6 @@
<string name="select_share.empty">Нет доступных ресурсов на сервере</string> <string name="select_share.empty">Нет доступных ресурсов на сервере</string>
<string name="menu_deleted_share">Удалить общий ресурс %s</string> <string name="menu_deleted_share">Удалить общий ресурс %s</string>
<string name="menu_deleted_share_error">Не удалось удалить общий ресурс %s</string> <string name="menu_deleted_share_error">Не удалось удалить общий ресурс %s</string>
<string name="settings.share_milliseconds">Миллисекунды</string>
<string name="settings.share_seconds">Секунды</string>
<string name="settings.share_minutes">Минуты</string> <string name="settings.share_minutes">Минуты</string>
<string name="settings.share_hours">Часы</string> <string name="settings.share_hours">Часы</string>
<string name="settings.share_days">Дни</string> <string name="settings.share_days">Дни</string>

View File

@ -318,8 +318,6 @@
<string name="select_share.empty">服务器上没有可用的共享</string> <string name="select_share.empty">服务器上没有可用的共享</string>
<string name="menu_deleted_share">删除分享 %s</string> <string name="menu_deleted_share">删除分享 %s</string>
<string name="menu_deleted_share_error">删除分享失败 %s</string> <string name="menu_deleted_share_error">删除分享失败 %s</string>
<string name="settings.share_milliseconds">毫秒</string>
<string name="settings.share_seconds"></string>
<string name="settings.share_minutes">分钟</string> <string name="settings.share_minutes">分钟</string>
<string name="settings.share_hours">小时</string> <string name="settings.share_hours">小时</string>
<string name="settings.share_days"></string> <string name="settings.share_days"></string>

View File

@ -218,8 +218,6 @@
<item>@string/settings.search_500</item> <item>@string/settings.search_500</item>
</string-array> </string-array>
<string-array name="shareExpirationNames" translatable="false"> <string-array name="shareExpirationNames" translatable="false">
<item>@string/settings.share_milliseconds</item>
<item>@string/settings.share_seconds</item>
<item>@string/settings.share_minutes</item> <item>@string/settings.share_minutes</item>
<item>@string/settings.share_hours</item> <item>@string/settings.share_hours</item>
<item>@string/settings.share_days</item> <item>@string/settings.share_days</item>

View File

@ -353,8 +353,6 @@
<string name="select_share.empty">No shares available on server</string> <string name="select_share.empty">No shares available on server</string>
<string name="menu_deleted_share">Deleted share %s</string> <string name="menu_deleted_share">Deleted share %s</string>
<string name="menu_deleted_share_error">Failed to delete share %s</string> <string name="menu_deleted_share_error">Failed to delete share %s</string>
<string name="settings.share_milliseconds">Milliseconds</string>
<string name="settings.share_seconds">Seconds</string>
<string name="settings.share_minutes">Minutes</string> <string name="settings.share_minutes">Minutes</string>
<string name="settings.share_hours">Hours</string> <string name="settings.share_hours">Hours</string>
<string name="settings.share_days">Days</string> <string name="settings.share_days">Days</string>
@ -383,7 +381,7 @@
<string name="settings.debug.log_delete">Delete files</string> <string name="settings.debug.log_delete">Delete files</string>
<string name="settings.debug.log_deleted">Deleted log files.</string> <string name="settings.debug.log_deleted">Deleted log files.</string>
<string name="notification.downloading_title">Downloading media in the background…</string> <string name="notification.downloading_title">Downloading media in the background…</string>
<string name="notification.permission_required">Notifications are required for media playback. You can grant the permission anytime in the Android settings.</string>
<string name="server_selector.label">Configured servers</string> <string name="server_selector.label">Configured servers</string>
<string name="server_selector.delete_confirmation">Are you sure you want to delete the server?</string> <string name="server_selector.delete_confirmation">Are you sure you want to delete the server?</string>
<string name="server_editor.label">Editing server</string> <string name="server_editor.label">Editing server</string>