11 changed files with 450 additions and 0 deletions
@ -0,0 +1,9 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="960" |
|||
android:viewportHeight="960"> |
|||
<path |
|||
android:pathData="M480,624 L288,432l51,-51 105,105v-342h72v342l105,-105 51,51 -192,192ZM263.72,768Q234,768 213,746.85T192,696v-72h72v72h432v-72h72v72q0,29.7 -21.16,50.85Q725.68,768 695.96,768L263.72,768Z" |
|||
android:fillColor="#e8eaed"/> |
|||
</vector> |
|||
@ -0,0 +1,9 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:pathData="M12,6c3.79,0 7.17,2.13 8.82,5.5 -0.59,1.22 -1.42,2.27 -2.41,3.12l1.41,1.41c1.39,-1.23 2.49,-2.77 3.18,-4.53C21.27,7.11 17,4 12,4c-1.27,0 -2.49,0.2 -3.64,0.57l1.65,1.65C10.66,6.09 11.32,6 12,6zM10.93,7.14L13,9.21c0.57,0.25 1.03,0.71 1.28,1.28l2.07,2.07c0.08,-0.34 0.14,-0.7 0.14,-1.07C16.5,9.01 14.48,7 12,7c-0.37,0 -0.72,0.05 -1.07,0.14zM2.01,3.87l2.68,2.68C3.06,7.83 1.77,9.53 1,11.5 2.73,15.89 7,19 12,19c1.52,0 2.98,-0.29 4.32,-0.82l3.42,3.42 1.41,-1.41L3.42,2.45 2.01,3.87zM9.51,11.37l2.61,2.61c-0.04,0.01 -0.08,0.02 -0.12,0.02 -1.38,0 -2.5,-1.12 -2.5,-2.5 0,-0.05 0.01,-0.08 0.01,-0.13zM6.11,7.97l1.75,1.75c-0.23,0.55 -0.36,1.15 -0.36,1.78 0,2.48 2.02,4.5 4.5,4.5 0.63,0 1.23,-0.13 1.77,-0.36l0.98,0.98c-0.88,0.24 -1.8,0.38 -2.75,0.38 -3.79,0 -7.17,-2.13 -8.82,-5.5 0.7,-1.43 1.72,-2.61 2.93,-3.53z" |
|||
android:fillColor="#e8eaed"/> |
|||
</vector> |
|||
@ -0,0 +1,9 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:pathData="M12,6c3.79,0 7.17,2.13 8.82,5.5C19.17,14.87 15.79,17 12,17s-7.17,-2.13 -8.82,-5.5C4.83,8.13 8.21,6 12,6m0,-2C7,4 2.73,7.11 1,11.5 2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,9c1.38,0 2.5,1.12 2.5,2.5S13.38,14 12,14s-2.5,-1.12 -2.5,-2.5S10.62,9 12,9m0,-2c-2.48,0 -4.5,2.02 -4.5,4.5S9.52,16 12,16s4.5,-2.02 4.5,-4.5S14.48,7 12,7z" |
|||
android:fillColor="#e8eaed"/> |
|||
</vector> |
|||
@ -0,0 +1,137 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<layout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
xmlns:tools="http://schemas.android.com/tools"> |
|||
|
|||
<data> |
|||
|
|||
<import type="android.view.View" /> |
|||
<variable |
|||
name="equalizer" |
|||
type="org.videolan.vlc.mediadb.models.EqualizerWithBands" /> |
|||
|
|||
<variable |
|||
name="holder" |
|||
type="org.videolan.vlc.gui.EqualizerSettingsAdapter.ViewHolder" /> |
|||
|
|||
</data> |
|||
|
|||
|
|||
<androidx.constraintlayout.widget.ConstraintLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:gravity="center_horizontal" |
|||
android:clickable="true" |
|||
android:background="?android:attr/selectableItemBackground" |
|||
android:orientation="vertical" |
|||
android:focusable="true"> |
|||
|
|||
<ImageView |
|||
android:id="@+id/imageView22" |
|||
android:layout_width="40dp" |
|||
android:layout_height="40dp" |
|||
android:layout_marginStart="8dp" |
|||
android:layout_marginTop="8dp" |
|||
android:layout_marginBottom="8dp" |
|||
app:equalizerImage="@{equalizer}" |
|||
app:layout_constraintBottom_toBottomOf="parent" |
|||
app:layout_constraintStart_toStartOf="parent" |
|||
app:layout_constraintTop_toTopOf="parent" |
|||
tools:srcCompat="@tools:sample/avatars" /> |
|||
|
|||
<TextView |
|||
android:id="@+id/title" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginStart="16dp" |
|||
android:layout_marginEnd="8dp" |
|||
android:text="@{equalizer.equalizerEntry.name}" |
|||
android:textColor="?attr/font_default" |
|||
app:equalizerNameColor="@{equalizer}" |
|||
android:textSize="16sp" |
|||
app:layout_constraintBottom_toBottomOf="parent" |
|||
app:layout_constraintEnd_toStartOf="@+id/export" |
|||
app:layout_constraintStart_toEndOf="@+id/imageView22" |
|||
app:layout_constraintTop_toTopOf="parent" |
|||
tools:text="Classical" /> |
|||
|
|||
<androidx.appcompat.widget.AppCompatImageView |
|||
android:id="@+id/enable" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginTop="8dp" |
|||
android:layout_marginEnd="8dp" |
|||
android:layout_marginBottom="8dp" |
|||
android:background="?attr/actionBarItemBackground" |
|||
android:clickable="true" |
|||
android:contentDescription="@string/show_equalizer" |
|||
android:focusable="true" |
|||
android:onClick="@{holder::onClickEnable}" |
|||
android:padding="12dp" |
|||
android:visibility="@{equalizer.equalizerEntry.presetIndex != -1 && equalizer.equalizerEntry.isDisabled ? View.VISIBLE : View.GONE}" |
|||
app:layout_constraintBottom_toBottomOf="parent" |
|||
app:layout_constraintEnd_toStartOf="@+id/disable" |
|||
app:layout_constraintTop_toTopOf="parent" |
|||
app:srcCompat="@drawable/ic_invisible" |
|||
app:tint="?attr/colorControlNormal" /> |
|||
|
|||
<androidx.appcompat.widget.AppCompatImageView |
|||
android:id="@+id/disable" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginTop="8dp" |
|||
android:layout_marginEnd="8dp" |
|||
android:layout_marginBottom="8dp" |
|||
android:background="?attr/actionBarItemBackground" |
|||
android:onClick="@{holder::onClickDisable}" |
|||
android:clickable="true" |
|||
android:contentDescription="@string/hide_equalizer" |
|||
android:focusable="true" |
|||
android:padding="12dp" |
|||
android:visibility="@{equalizer.equalizerEntry.presetIndex == -1 || equalizer.equalizerEntry.isDisabled ? View.GONE : View.VISIBLE}" |
|||
app:layout_constraintBottom_toBottomOf="parent" |
|||
app:layout_constraintEnd_toEndOf="parent" |
|||
app:layout_constraintTop_toTopOf="parent" |
|||
app:srcCompat="@drawable/ic_visible" |
|||
app:tint="?attr/colorControlNormal" /> |
|||
|
|||
<androidx.appcompat.widget.AppCompatImageView |
|||
android:id="@+id/delete" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginEnd="8dp" |
|||
android:background="?attr/actionBarItemBackground" |
|||
android:clickable="true" |
|||
android:contentDescription="@string/delete" |
|||
android:onClick="@{holder::onClickDelete}" |
|||
android:focusable="true" |
|||
android:padding="12dp" |
|||
android:visibility="@{equalizer.equalizerEntry.presetIndex == -1 ? View.VISIBLE : View.GONE}" |
|||
app:layout_constraintBottom_toBottomOf="@+id/enable" |
|||
app:layout_constraintEnd_toStartOf="@+id/enable" |
|||
app:layout_constraintTop_toTopOf="@+id/enable" |
|||
app:srcCompat="@drawable/ic_delete" |
|||
app:tint="?attr/colorControlNormal" /> |
|||
|
|||
<androidx.appcompat.widget.AppCompatImageView |
|||
android:id="@+id/export" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_marginEnd="8dp" |
|||
android:background="?attr/actionBarItemBackground" |
|||
android:clickable="true" |
|||
android:contentDescription="@string/export_equalizer" |
|||
android:onClick="@{holder::onClickExport}" |
|||
android:focusable="true" |
|||
android:padding="12dp" |
|||
android:visibility="@{equalizer.equalizerEntry.presetIndex == -1 ? View.VISIBLE : View.GONE}" |
|||
app:layout_constraintBottom_toBottomOf="@+id/delete" |
|||
app:layout_constraintEnd_toStartOf="@+id/delete" |
|||
app:layout_constraintTop_toTopOf="@+id/delete" |
|||
app:srcCompat="@drawable/ic_export" |
|||
app:tint="?attr/colorControlNormal" /> |
|||
|
|||
|
|||
</androidx.constraintlayout.widget.ConstraintLayout> |
|||
|
|||
</layout> |
|||
@ -0,0 +1,40 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<layout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:vlc="http://schemas.android.com/apk/res-auto"> |
|||
|
|||
<data> |
|||
|
|||
</data> |
|||
|
|||
<merge |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent"> |
|||
|
|||
<androidx.coordinatorlayout.widget.CoordinatorLayout |
|||
android:id="@+id/coordinator" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent"> |
|||
|
|||
<include |
|||
layout="@layout/toolbar" |
|||
android:nextFocusLeft="@+id/ml_menu_search" |
|||
android:nextFocusRight="@+id/ml_menu_search" |
|||
android:nextFocusUp="@+id/ml_menu_search" |
|||
android:nextFocusDown="@+id/ml_menu_search" |
|||
android:nextFocusForward="@+id/ml_menu_search" /> |
|||
|
|||
<androidx.recyclerview.widget.RecyclerView |
|||
android:id="@+id/equalizers" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:layout_gravity="center_horizontal|top" |
|||
android:background="?attr/background_default" |
|||
android:clipToPadding="false" |
|||
android:paddingBottom="48dp" |
|||
android:fastScrollEnabled="true" |
|||
android:scrollbars="vertical" |
|||
vlc:layout_behavior="@string/appbar_scrolling_view_behavior" /> |
|||
|
|||
</androidx.coordinatorlayout.widget.CoordinatorLayout> |
|||
</merge> |
|||
</layout> |
|||
@ -0,0 +1,229 @@ |
|||
package org.videolan.vlc.gui |
|||
|
|||
import android.content.Context |
|||
import android.graphics.Bitmap |
|||
import android.graphics.Canvas |
|||
import android.graphics.Paint |
|||
import android.graphics.Path |
|||
import android.graphics.Rect |
|||
import android.graphics.RectF |
|||
import android.os.Bundle |
|||
import android.util.Log |
|||
import android.util.TypedValue |
|||
import android.view.LayoutInflater |
|||
import android.view.MenuItem |
|||
import android.view.View |
|||
import android.view.ViewGroup |
|||
import android.widget.ImageView |
|||
import android.widget.TextView |
|||
import androidx.activity.viewModels |
|||
import androidx.annotation.ColorInt |
|||
import androidx.core.content.ContextCompat |
|||
import androidx.core.graphics.createBitmap |
|||
import androidx.databinding.BindingAdapter |
|||
import androidx.databinding.DataBindingUtil |
|||
import androidx.lifecycle.Observer |
|||
import androidx.lifecycle.ViewModel |
|||
import androidx.lifecycle.ViewModelProvider |
|||
import androidx.lifecycle.asLiveData |
|||
import androidx.lifecycle.viewModelScope |
|||
import androidx.recyclerview.widget.LinearLayoutManager |
|||
import com.google.android.material.appbar.MaterialToolbar |
|||
import kotlinx.coroutines.Dispatchers |
|||
import kotlinx.coroutines.launch |
|||
import org.videolan.resources.AndroidDevices |
|||
import org.videolan.resources.util.applyOverscanMargin |
|||
import org.videolan.tools.dp |
|||
import org.videolan.vlc.BuildConfig |
|||
import org.videolan.vlc.R |
|||
import org.videolan.vlc.databinding.EqualizerSettingItemBinding |
|||
import org.videolan.vlc.databinding.EqualizerSettingsActivityBinding |
|||
import org.videolan.vlc.gui.helpers.SelectorViewHolder |
|||
import org.videolan.vlc.gui.helpers.UiTools |
|||
import org.videolan.vlc.mediadb.models.EqualizerWithBands |
|||
import org.videolan.vlc.repository.EqualizerRepository |
|||
|
|||
|
|||
/** |
|||
* Equalizer settings activity allowing to enable/disable/delete/export/import the presets |
|||
* |
|||
* @constructor Create empty Equalizer settings activity |
|||
*/ |
|||
class EqualizerSettingsActivity : BaseActivity() { |
|||
|
|||
private lateinit var adapter: EqualizerSettingsAdapter |
|||
private val model: EqualizerSettingsModel by viewModels { |
|||
EqualizerSettingsModelFactory(this, EqualizerRepository.getInstance(application)) |
|||
} |
|||
|
|||
internal lateinit var binding: EqualizerSettingsActivityBinding |
|||
override fun getSnackAnchorView(overAudioPlayer: Boolean) = binding.root |
|||
override val displayTitle = true |
|||
|
|||
override fun onCreate(savedInstanceState: Bundle?) { |
|||
super.onCreate(savedInstanceState) |
|||
|
|||
binding = DataBindingUtil.setContentView(this, R.layout.equalizer_settings_activity) |
|||
val toolbar = findViewById<MaterialToolbar>(R.id.main_toolbar) |
|||
setSupportActionBar(toolbar) |
|||
supportActionBar!!.setDisplayHomeAsUpEnabled(true) |
|||
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_close_up) |
|||
title = getString(R.string.equalizer) |
|||
|
|||
binding.equalizers.layoutManager = LinearLayoutManager(this) |
|||
adapter = EqualizerSettingsAdapter { equalizer, type -> |
|||
when (type) { |
|||
ClickType.EQUALIZER_ENABLE -> model.enable(this, equalizer) |
|||
ClickType.EQUALIZER_DISABLE -> model.disable(this, equalizer) |
|||
ClickType.EQUALIZER_DELETE -> { |
|||
model.delete(this, equalizer) |
|||
UiTools.snackerConfirm(this, getString(R.string.equalizer_deleted), confirmMessage = R.string.undo) { |
|||
model.restore(this) |
|||
} |
|||
} |
|||
|
|||
ClickType.EQUALIZER_EXPORT -> model.export(this, equalizer) |
|||
} |
|||
} |
|||
binding.equalizers.adapter = adapter |
|||
|
|||
model.equalizerEntries.observe(this, Observer { |
|||
adapter.update(it) |
|||
}) |
|||
if (AndroidDevices.isTv) applyOverscanMargin(this) |
|||
|
|||
} |
|||
|
|||
override fun onOptionsItemSelected(item: MenuItem): Boolean { |
|||
when (item.itemId) { |
|||
android.R.id.home -> finish() |
|||
} |
|||
return super.onOptionsItemSelected(item) |
|||
} |
|||
|
|||
} |
|||
|
|||
class EqualizerSettingsModel(private val equalizerRepository: EqualizerRepository) : ViewModel() { |
|||
private var oldEqualizer: EqualizerWithBands? = null |
|||
|
|||
fun enable(context: Context, equalizer: EqualizerWithBands) = viewModelScope.launch(Dispatchers.IO) { |
|||
equalizerRepository.addOrUpdateEqualizerWithBands(context, equalizer.copy(equalizerEntry = equalizer.equalizerEntry.copy(isDisabled = false).apply { id = equalizer.equalizerEntry.id })) |
|||
} |
|||
|
|||
fun disable(context: Context, equalizer: EqualizerWithBands) = viewModelScope.launch(Dispatchers.IO) { |
|||
equalizerRepository.addOrUpdateEqualizerWithBands(context, equalizer.copy(equalizerEntry = equalizer.equalizerEntry.copy(isDisabled = true).apply { id = equalizer.equalizerEntry.id })) |
|||
} |
|||
|
|||
fun delete(context: Context, equalizer: EqualizerWithBands) = viewModelScope.launch(Dispatchers.IO) { |
|||
oldEqualizer = equalizer |
|||
equalizerRepository.delete(equalizer.equalizerEntry) |
|||
} |
|||
|
|||
fun restore(context: Context) = viewModelScope.launch(Dispatchers.IO) { |
|||
equalizerRepository.addOrUpdateEqualizerWithBands(context, oldEqualizer!!) |
|||
} |
|||
|
|||
fun export(context: Context, equalizer: EqualizerWithBands) = viewModelScope.launch(Dispatchers.IO) { |
|||
//todo add export |
|||
} |
|||
|
|||
val equalizerEntries = equalizerRepository.equalizerEntriesUnfiltered.asLiveData() |
|||
} |
|||
|
|||
class EqualizerSettingsModelFactory(private val context: Context, private val repository: EqualizerRepository) : ViewModelProvider.Factory { |
|||
override fun <T : ViewModel> create(modelClass: Class<T>): T { |
|||
if (modelClass.isAssignableFrom(EqualizerSettingsModel::class.java)) { |
|||
@Suppress("UNCHECKED_CAST") |
|||
return EqualizerSettingsModel(repository) as T |
|||
} |
|||
throw IllegalArgumentException("Unknown ViewModel class") |
|||
} |
|||
} |
|||
|
|||
class EqualizerSettingsAdapter(private val itemClickHandler: (equalizer: EqualizerWithBands, ClickType) -> Unit) : DiffUtilAdapter<EqualizerWithBands, EqualizerSettingsAdapter.ViewHolder>() { |
|||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { |
|||
return ViewHolder(EqualizerSettingItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)) |
|||
} |
|||
|
|||
override fun onBindViewHolder(holder: ViewHolder, position: Int) { |
|||
holder.binding.equalizer = dataset[position] |
|||
} |
|||
|
|||
override fun getItemCount() = dataset.size |
|||
|
|||
inner class ViewHolder(vdb: EqualizerSettingItemBinding) : SelectorViewHolder<EqualizerSettingItemBinding>(vdb) { |
|||
init { |
|||
binding.holder = this |
|||
} |
|||
|
|||
fun onClickEnable(@Suppress("UNUSED_PARAMETER") v: View) { |
|||
itemClickHandler.invoke(dataset[layoutPosition], ClickType.EQUALIZER_ENABLE) |
|||
} |
|||
|
|||
fun onClickDisable(@Suppress("UNUSED_PARAMETER") v: View) { |
|||
itemClickHandler.invoke(dataset[layoutPosition], ClickType.EQUALIZER_DISABLE) |
|||
} |
|||
|
|||
fun onClickDelete(@Suppress("UNUSED_PARAMETER") v: View) { |
|||
itemClickHandler.invoke(dataset[layoutPosition], ClickType.EQUALIZER_DELETE) |
|||
} |
|||
|
|||
fun onClickExport(@Suppress("UNUSED_PARAMETER") v: View) { |
|||
itemClickHandler.invoke(dataset[layoutPosition], ClickType.EQUALIZER_EXPORT) |
|||
} |
|||
} |
|||
} |
|||
|
|||
enum class ClickType { |
|||
EQUALIZER_DISABLE, EQUALIZER_ENABLE, EQUALIZER_DELETE, EQUALIZER_EXPORT |
|||
} |
|||
|
|||
fun EqualizerWithBands.getBitmap(context: Context): Bitmap { |
|||
val imageSize = 72.dp |
|||
val step = 8.dp |
|||
|
|||
val typedValue = TypedValue() |
|||
val theme = context.theme |
|||
theme.resolveAttribute(R.attr.default_divider, typedValue, true) |
|||
@ColorInt val color: Int = typedValue.data |
|||
|
|||
|
|||
val bitmap = createBitmap(imageSize, imageSize) |
|||
val canvas = Canvas(bitmap) |
|||
val paint = Paint() |
|||
|
|||
val path = Path() |
|||
path.addRoundRect(RectF(0F, 0F, imageSize.toFloat(), imageSize.toFloat()), step.toFloat(), step.toFloat(), Path.Direction.CCW) |
|||
|
|||
canvas.clipPath(path) |
|||
|
|||
|
|||
paint.color = if (equalizerEntry.presetIndex == -1) ContextCompat.getColor(context, R.color.orange500focus) else color |
|||
var x = 0 |
|||
bands.sortedBy { it.index }.forEach { |
|||
val heightPc = (it.bandValue + 20) / 40 |
|||
if (BuildConfig.DEBUG) Log.d("BandBitmap", "band value is ${it.bandValue} and height is ${heightPc * 38}") |
|||
canvas.drawRect(Rect(x, imageSize, x + step, imageSize - (66 * heightPc).toInt().dp), paint) |
|||
x += step |
|||
} |
|||
paint.color = color |
|||
|
|||
canvas.drawPath(path, paint) |
|||
return bitmap |
|||
} |
|||
|
|||
@BindingAdapter("equalizerImage") |
|||
fun equalizerImage(view: ImageView, item: EqualizerWithBands) { |
|||
view.setImageBitmap(item.getBitmap(view.context)) |
|||
} |
|||
|
|||
@BindingAdapter("equalizerNameColor") |
|||
fun equalizerNameColor(view: TextView, item: EqualizerWithBands) { |
|||
val typedValue = TypedValue() |
|||
val theme = view.context.theme |
|||
theme.resolveAttribute(if (item.equalizerEntry.isDisabled)R.attr.font_disabled else R.attr.font_default, typedValue, true) |
|||
@ColorInt val color: Int = typedValue.data |
|||
view.setTextColor(color) |
|||
} |
|||
|
|||
|
|||
Loading…
Reference in new issue