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"
room = "2.4.3"
kotlin = "1.7.10"
kotlinxCoroutines = "1.6.3-native-mt"
kotlinxGuava = "1.6.3"
kotlinxCoroutines = "1.6.4-native-mt"
kotlinxGuava = "1.6.4"
viewModelKtx = "2.5.1"
retrofit = "2.9.0"

View File

@ -1,5 +1,5 @@
<?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
id="PluralsCandidate"
@ -737,7 +737,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/res/layout/equalizer_bar.xml"
line="19"
line="26"
column="13"/>
</issue>

View File

@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET"/>
<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.READ_EXTERNAL_STORAGE"/>
<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()
}
// Ask for permission to send notifications
Util.ensurePermissionToPostNotification(this)
RxBus.dismissNowPlayingCommandObservable.subscribe {
nowPlayingHidden = true
hideNowPlaying()

View File

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

View File

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

View File

@ -7,9 +7,11 @@
package org.moire.ultrasonic.imageloader
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.BitmapLoader
import com.google.common.util.concurrent.ListenableFuture
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.inject
@SuppressLint("UnsafeOptInUsageError")
class ArtworkBitmapLoader : BitmapLoader, KoinComponent {
private val imageLoader: ImageLoader by inject()

View File

@ -7,11 +7,11 @@
package org.moire.ultrasonic.playback
import android.annotation.SuppressLint
import android.content.Context
import androidx.core.app.NotificationCompat
import androidx.media3.common.HeartRating
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.CommandButton
import androidx.media3.session.DefaultMediaNotificationProvider
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.util.toTrack
@UnstableApi
@SuppressLint("UnsafeOptInUsageError")
class MediaNotificationProvider(context: Context) :
DefaultMediaNotificationProvider(context, ArtworkBitmapLoader()), KoinComponent {

View File

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

View File

@ -39,7 +39,6 @@ import androidx.media3.common.Timeline
import androidx.media3.common.TrackSelectionParameters
import androidx.media3.common.VideoSize
import androidx.media3.common.text.CueGroup
import androidx.media3.common.util.Util
import androidx.media3.session.MediaSession
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.ListenableFuture
@ -76,6 +75,7 @@ private const val SEEK_START_AFTER_SECONDS = 5
* TODO: Minimize status updates.
*/
@Suppress("TooManyFunctions")
@SuppressLint("UnsafeOptInUsageError")
class JukeboxMediaPlayer : JukeboxUnimplementedFunctions(), Player {
private val tasks = TaskQueue()
private val executorService = Executors.newSingleThreadScheduledExecutor()
@ -119,7 +119,7 @@ class JukeboxMediaPlayer : JukeboxUnimplementedFunctions(), Player {
JukeboxNotificationActionFactory()
) {}
if (Util.SDK_INT >= 29) {
if (Build.VERSION.SDK_INT >= 29) {
startForeground(
notification.notificationId,
notification.notification,
@ -140,7 +140,12 @@ class JukeboxMediaPlayer : JukeboxUnimplementedFunctions(), Player {
val extras = intent.extras
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) {
KEYCODE_MEDIA_PLAY -> play()
KEYCODE_MEDIA_PAUSE -> stop()

View File

@ -7,6 +7,7 @@
package org.moire.ultrasonic.service
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.ComponentName
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
* of the Jukebox Service
*/
@SuppressLint("UnsafeOptInUsageError")
class JukeboxNotificationActionFactory : MediaNotification.ActionFactory {
override fun createMediaAction(
mediaSession: MediaSession,

View File

@ -1,5 +1,5 @@
/*
* JukeboxUnimplemented.kt
* JukeboxUnimplementedFunctions.kt
* Copyright (C) 2009-2022 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
@ -7,6 +7,7 @@
package org.moire.ultrasonic.service
import android.annotation.SuppressLint
import android.app.Service
import android.view.Surface
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.
*/
@Suppress("TooManyFunctions")
@SuppressLint("UnsafeOptInUsageError")
abstract class JukeboxUnimplementedFunctions : Service(), Player {
override fun setMediaItems(mediaItems: MutableList<MediaItem>) {

View File

@ -7,6 +7,7 @@
package org.moire.ultrasonic.service
import android.annotation.SuppressLint
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
@ -20,6 +21,7 @@ import java.util.Arrays
* This class wraps a simple playlist provided as List<MediaItem>
* to be usable as a Media3 Timeline.
*/
@SuppressLint("UnsafeOptInUsageError")
class PlaylistTimeline @JvmOverloads constructor(
mediaItems: List<MediaItem>,
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.Settings
import org.moire.ultrasonic.util.ShareDetails
import org.moire.ultrasonic.util.TimeSpan
import org.moire.ultrasonic.util.TimeSpanPicker
import org.moire.ultrasonic.util.Util.ifNotNull
@ -145,9 +144,8 @@ class ShareHandler(val context: Context) {
showDialog(fragment, shareDetails, swipe, cancellationToken, additionalId)
} else {
shareDetails.Description = Settings.defaultShareDescription
shareDetails.Expiration = TimeSpan.getCurrentTime().add(
shareDetails.Expiration = System.currentTimeMillis() +
Settings.defaultShareExpirationInMillis
).totalMilliseconds
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
hideDialogCheckBox = layout.findViewById<View>(R.id.hide_dialog) 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
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
textViewExpiration = layout.findViewById<View>(R.id.textViewExpiration) as TextView
}
@ -191,9 +189,8 @@ class ShareHandler(val context: Context) {
builder.setPositiveButton(R.string.menu_share) { _, _ ->
if (!noExpirationCheckBox!!.isChecked) {
val timeSpan: TimeSpan = timeSpanPicker!!.timeSpan
val now = TimeSpan.getCurrentTime()
shareDetails.Expiration = now.add(timeSpan).totalMilliseconds
val timeSpan: Long = timeSpanPicker!!.getTimeSpan()
shareDetails.Expiration = System.currentTimeMillis() + timeSpan
}
shareDetails.Description = shareDescription!!.text.toString()
@ -204,7 +201,7 @@ class ShareHandler(val context: Context) {
}
if (saveAsDefaultsCheckBox!!.isChecked) {
val timeSpanType: String = timeSpanPicker!!.timeSpanType
val timeSpanType: String = timeSpanPicker!!.timeSpanType!!
val timeSpanAmount: Int = timeSpanPicker!!.timeSpanAmount
Settings.defaultShareExpiration =
if (!noExpirationCheckBox!!.isChecked && timeSpanAmount > 0)
@ -240,7 +237,7 @@ class ShareHandler(val context: Context) {
noExpirationCheckBox!!.isChecked = false
timeSpanPicker!!.isEnabled = true
timeSpanPicker!!.setTimeSpanAmount(timeSpanAmount.toString())
timeSpanPicker!!.setTimeSpanType(timeSpanType)
timeSpanPicker!!.timeSpanType = timeSpanType
} else {
noExpirationCheckBox!!.isChecked = true
timeSpanPicker!!.isEnabled = false

View File

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

View File

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

View File

@ -7,6 +7,7 @@
package org.moire.ultrasonic.util
import android.Manifest.permission.POST_NOTIFICATIONS
import android.annotation.SuppressLint
import android.app.Activity
import android.app.PendingIntent
@ -32,7 +33,10 @@ import android.util.TypedValue
import android.view.Gravity
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.AnyRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.media3.common.C
import androidx.media3.common.MediaItem
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
@Suppress("DEPRECATION")
fun getVersionName(context: Context): String? {

View File

@ -1,11 +1,18 @@
<?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"
a:orientation="vertical"
a:layout_width="fill_parent"
a:layout_height="wrap_content">
<TextView
a:id="@+id/equalizer.frequency"
a:id="@+id/equalizer_frequency"
a:textSize="12sp"
a:textColor="#c0c0c0"
a:layout_width="wrap_content"
@ -15,7 +22,7 @@
/>
<TextView
a:id="@+id/equalizer.level"
a:id="@+id/equalizer_level"
a:text="0 dB"
a:textSize="12sp"
a:textColor="#c0c0c0"
@ -24,14 +31,14 @@
a:layout_height="wrap_content"
a:layout_marginTop="8dp"
a:layout_alignParentRight="true"
a:layout_toEndOf="@+id/equalizer.frequency"
a:layout_toEndOf="@+id/equalizer_frequency"
/>
<SeekBar
a:id="@+id/equalizer.bar"
a:id="@+id/equalizer_bar"
a:layout_width="fill_parent"
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="menu_deleted_share">Smazaná 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_hours">Hodin</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="menu_deleted_share">Freigabe %s gelöscht</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_hours">Stunden</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="menu_deleted_share">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_hours">Horas</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="menu_deleted_share">Partage supprimé %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_hours">Heures</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="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="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_hours">Óra</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="settings.share_description_default">Descrizione Predefinita di Condivisione</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_hours">Ore</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="menu_deleted_share">Deling %s verwijderd</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_hours">Uur</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="menu_deleted_share">Usunięto udostępnienie %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_hours">Godziny</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="menu_deleted_share">Compartilhamento %s excluído</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_hours">Horas</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="menu_deleted_share">Compartilhamento %s apagado</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_hours">Horas</string>
<string name="settings.share_days">Dias</string>

View File

@ -318,8 +318,6 @@
<string name="select_share.empty">Нет доступных ресурсов на сервере</string>
<string name="menu_deleted_share">Удалить общий ресурс %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_hours">Часы</string>
<string name="settings.share_days">Дни</string>

View File

@ -318,8 +318,6 @@
<string name="select_share.empty">服务器上没有可用的共享</string>
<string name="menu_deleted_share">删除分享 %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_hours">小时</string>
<string name="settings.share_days"></string>

View File

@ -218,8 +218,6 @@
<item>@string/settings.search_500</item>
</string-array>
<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_hours</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="menu_deleted_share">Deleted 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_hours">Hours</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_deleted">Deleted log files.</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.delete_confirmation">Are you sure you want to delete the server?</string>
<string name="server_editor.label">Editing server</string>