diff --git a/FAQ.md b/FAQ.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/LIBRARIES.md b/LIBRARIES.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/LICENSES.md b/LICENSES.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/app/build.gradle b/app/build.gradle
index e899774c8..cf6b96fd2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -51,13 +51,14 @@ dependencies {
// Kotlin
//noinspection DifferentStdlibGradleVersion
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// --- SUPPORT ---
// General
implementation 'androidx.core:core-ktx:1.3.2'
- implementation 'androidx.activity:activity-ktx:1.2.0-beta01'
- implementation 'androidx.fragment:fragment-ktx:1.3.0-beta01'
+ implementation 'androidx.activity:activity-ktx:1.2.0-beta02'
+ implementation 'androidx.fragment:fragment-ktx:1.3.0-beta02'
// Layout
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
@@ -79,6 +80,9 @@ dependencies {
// Preferences
implementation 'androidx.preference:preference-ktx:1.1.1'
+ // Opening links
+ implementation 'androidx.browser:browser:1.3.0'
+
// --- THIRD PARTY ---
// ExoPlayer
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 555b5640f..4dcb7ac91 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -7,6 +7,8 @@
+
+
val top = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
insets.getInsets(WindowInsets.Type.systemBars()).top
diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt
index 962e92a4a..53ef3607c 100644
--- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt
@@ -5,7 +5,9 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
+import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentSettingsBinding
+import org.oxycblt.auxio.settings.ui.AboutDialog
class SettingsFragment : Fragment() {
override fun onCreateView(
@@ -15,6 +17,13 @@ class SettingsFragment : Fragment() {
): View {
val binding = FragmentSettingsBinding.inflate(inflater)
+ binding.settingsToolbar.setOnMenuItemClickListener {
+ if (it.itemId == R.id.action_open_about) {
+ AboutDialog().show(childFragmentManager, "DIALOG")
+ true
+ } else false
+ }
+
return binding.root
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt
index 24ea66103..32fa708a1 100644
--- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt
@@ -17,7 +17,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.DisplayMode
-import org.oxycblt.auxio.settings.adapters.AccentAdapter
+import org.oxycblt.auxio.settings.ui.AccentAdapter
import org.oxycblt.auxio.ui.ACCENTS
import org.oxycblt.auxio.ui.accent
import org.oxycblt.auxio.ui.createToast
diff --git a/app/src/main/java/org/oxycblt/auxio/settings/ui/AboutDialog.kt b/app/src/main/java/org/oxycblt/auxio/settings/ui/AboutDialog.kt
new file mode 100644
index 000000000..4a9174969
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/auxio/settings/ui/AboutDialog.kt
@@ -0,0 +1,121 @@
+package org.oxycblt.auxio.settings.ui
+
+import android.app.Dialog
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import androidx.browser.customtabs.CustomTabsIntent
+import androidx.core.net.toUri
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import org.oxycblt.auxio.BuildConfig
+import org.oxycblt.auxio.R
+import org.oxycblt.auxio.databinding.FragmentAboutBinding
+import org.oxycblt.auxio.logD
+import org.oxycblt.auxio.logE
+import org.oxycblt.auxio.music.MusicStore
+import org.oxycblt.auxio.settings.SettingsManager
+import org.oxycblt.auxio.ui.createToast
+import org.oxycblt.auxio.ui.handleTransparentSystemBars
+import org.oxycblt.auxio.ui.handleTransparentSystemNavBar
+import org.oxycblt.auxio.ui.isIrregularLandscape
+import org.oxycblt.auxio.ui.isLandscape
+import org.oxycblt.auxio.ui.toColor
+import java.lang.Exception
+
+class AboutDialog : BottomSheetDialogFragment() {
+ override fun getTheme() = R.style.Theme_BottomSheetFix
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val binding = FragmentAboutBinding.inflate(layoutInflater)
+ val musicStore = MusicStore.getInstance()
+ val settingsManager = SettingsManager.getInstance()
+
+ // --- UI SETUP ---
+
+ // Apply edge-to-edge to the dialog if supported/enabled.
+ if (settingsManager.edgeEnabled && !requireActivity().isIrregularLandscape()) {
+ requireDialog().window?.apply {
+ navigationBarColor = R.color.background.toColor(requireContext())
+ handleTransparentSystemNavBar(resources.configuration)
+ }
+ }
+
+ binding.aboutVersion.text = BuildConfig.VERSION_NAME
+
+ binding.aboutCode.setOnClickListener { openLinkInBrowser(LINK_CODEBASE) }
+ binding.aboutFaq.setOnClickListener { openLinkInBrowser(LINK_FAQ) }
+ binding.aboutLicenses.setOnClickListener { openLinkInBrowser(LINK_LICENSES) }
+
+ binding.aboutSongCount.text = getString(
+ R.string.format_songs_loaded, musicStore.songs.size.toString()
+ )
+ binding.aboutAuthor.text = getString(
+ R.string.format_author, getString(R.string.author_oxycblt)
+ )
+
+ logD("Dialog created.")
+
+ return binding.root
+ }
+
+ private fun openLinkInBrowser(link: String) {
+ check(link in LINKS) { "Invalid link." }
+
+ try {
+ val tabsIntent = CustomTabsIntent.Builder()
+ .setShareState(CustomTabsIntent.SHARE_STATE_ON)
+ .setShowTitle(true)
+ .build()
+
+ val uri = link.toUri()
+ val manager = requireActivity().packageManager
+ val browserCandidates = manager.queryIntentActivities(tabsIntent.intent, 0)
+
+ // If there are candidates for this link, then launch it through that.
+ if (browserCandidates.size > 0) {
+ tabsIntent.launchUrl(requireActivity(), uri)
+ } else {
+ // If there are no candidates, then fallback to another list of browsers
+
+ val browserIntent = Intent(Intent.ACTION_VIEW, uri)
+ browserIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+
+ val fallbackCandidates = manager.queryIntentActivities(browserIntent, 0)
+
+ // If there are candidates here, then launch those.
+ if (fallbackCandidates.size > 0) {
+ requireActivity().startActivity(browserIntent)
+ } else {
+ // Otherwise they don't have a browser on their phone, meaning they should
+ // just see an error.
+ getString(R.string.error_no_browser).createToast(requireContext())
+ }
+ }
+ } catch (e: Exception) {
+ logE("Browser intent launching failed [Probably android's fault]")
+ e.printStackTrace()
+
+ // Sometimes people have """""Browsers""""" on their phone according to android,
+ // but they actually don't so here's a fallback for that.
+ getString(R.string.error_no_browser).createToast(requireContext())
+ }
+ }
+
+ companion object {
+ // TODO: Change dev to master
+ private const val LINK_CODEBASE = "https://github.com/oxygencobalt/Auxio"
+ private const val LINK_FAQ = "$LINK_CODEBASE/blob/master/FAQ.md"
+ private const val LINK_LICENSES = "$LINK_CODEBASE/blob/master/LICENSES.md"
+
+ val LINKS = arrayOf(LINK_CODEBASE, LINK_FAQ, LINK_LICENSES)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/oxycblt/auxio/settings/adapters/AccentAdapter.kt b/app/src/main/java/org/oxycblt/auxio/settings/ui/AccentAdapter.kt
similarity index 97%
rename from app/src/main/java/org/oxycblt/auxio/settings/adapters/AccentAdapter.kt
rename to app/src/main/java/org/oxycblt/auxio/settings/ui/AccentAdapter.kt
index 133dc9354..69b7c849c 100644
--- a/app/src/main/java/org/oxycblt/auxio/settings/adapters/AccentAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/settings/ui/AccentAdapter.kt
@@ -1,4 +1,4 @@
-package org.oxycblt.auxio.settings.adapters
+package org.oxycblt.auxio.settings.ui
import android.content.res.ColorStateList
import android.view.LayoutInflater
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/EdgeUtils.kt b/app/src/main/java/org/oxycblt/auxio/ui/EdgeUtils.kt
index c3ef8ddc8..f287ad152 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/EdgeUtils.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/EdgeUtils.kt
@@ -22,9 +22,8 @@ import org.oxycblt.auxio.settings.SettingsManager
* TODO: Make edge-to-edge work in irregular mode
* @return True if we are in the irregular landscape mode, false if not.
*/
-fun Activity.isInIrregularLandscapeMode(): Boolean {
- return SettingsManager.getInstance().edgeEnabled &&
- isLandscape(resources) &&
+fun Activity.isIrregularLandscape(): Boolean {
+ return isLandscape(resources) &&
!isSystemBarOnBottom(this)
}
@@ -93,3 +92,31 @@ fun Window.handleTransparentSystemBars(config: Configuration) {
}
}
}
+
+/**
+ * Handle only the transparent navigation bar.
+ */
+fun Window.handleTransparentSystemNavBar(config: Configuration) {
+ fun isNight() = config.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ insetsController?.let { controller ->
+ val appearance = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+
+ val mask = if (isNight()) 0 else appearance
+
+ controller.setSystemBarsAppearance(appearance, mask)
+ }
+ } else {
+ val flags = decorView.systemUiVisibility
+
+ decorView.systemUiVisibility =
+ if (isNight()) {
+ flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() and
+ View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
+ } else {
+ flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or
+ View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/FragmentBinderDelegate.kt b/app/src/main/java/org/oxycblt/auxio/ui/FragmentBinderDelegate.kt
index 19bad4110..55d420d5c 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/FragmentBinderDelegate.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/FragmentBinderDelegate.kt
@@ -16,20 +16,21 @@ import kotlin.reflect.KProperty
/**
* A delegate that creates a binding that can be used as a member variable without nullability or
* memory leaks.
+ * @param bindingFactory The ViewBinding inflation method that should be used
+ * @param onDestroy Any code that should be run when the binding is destroyed.
*/
fun Fragment.memberBinding(
- viewBindingFactory: (LayoutInflater) -> T,
- disposeEvents: T.() -> Unit = {}
-) = FragmentBinderDelegate(this, viewBindingFactory, disposeEvents)
+ bindingFactory: (LayoutInflater) -> T, onDestroy: T.() -> Unit = {}
+) = FragmentBinderDelegate(this, bindingFactory, onDestroy)
/**
- * The delegate for the [binder] shortcut function.
+ * The delegate for the [memberBinding] shortcut function.
* Adapted from KAHelpers (https://github.com/FunkyMuse/KAHelpers/tree/master/viewbinding)
*/
class FragmentBinderDelegate(
private val fragment: Fragment,
- private val binder: (LayoutInflater) -> T,
- private val disposeEvents: T.() -> Unit
+ private val inflate: (LayoutInflater) -> T,
+ private val onDestroy: T.() -> Unit
) : ReadOnlyProperty, LifecycleObserver {
private var fragmentBinding: T? = null
@@ -46,8 +47,8 @@ class FragmentBinderDelegate(
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
- viewLifecycleOwnerLiveData.observe(this@observeOwnerThroughCreation) { owner ->
- owner.viewOwner()
+ viewLifecycleOwnerLiveData.observe(this@observeOwnerThroughCreation) {
+ it.viewOwner()
}
}
})
@@ -69,13 +70,13 @@ class FragmentBinderDelegate(
throw IllegalStateException("Fragment views are destroyed.")
}
- return binder(LayoutInflater.from(thisRef.requireContext())).also { fragmentBinding = it }
+ return inflate(LayoutInflater.from(thisRef.requireContext())).also { fragmentBinding = it }
}
@Suppress("UNUSED")
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun dispose() {
- fragmentBinding?.disposeEvents()
+ fragmentBinding?.onDestroy()
fragmentBinding = null
}
}
diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml
new file mode 100644
index 000000000..dac4d0003
--- /dev/null
+++ b/app/src/main/res/drawable/ic_about.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_author.xml b/app/src/main/res/drawable/ic_author.xml
new file mode 100644
index 000000000..c94eabdde
--- /dev/null
+++ b/app/src/main/res/drawable/ic_author.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_code.xml b/app/src/main/res/drawable/ic_code.xml
new file mode 100644
index 000000000..fc2fa993b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_code.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_libraries.xml b/app/src/main/res/drawable/ic_libraries.xml
new file mode 100644
index 000000000..4f91aa912
--- /dev/null
+++ b/app/src/main/res/drawable/ic_libraries.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_license.xml b/app/src/main/res/drawable/ic_license.xml
new file mode 100644
index 000000000..eb899c8e0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_license.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_version.xml b/app/src/main/res/drawable/ic_version.xml
new file mode 100644
index 000000000..20ecccdf7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_version.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml
new file mode 100644
index 000000000..80a33e59d
--- /dev/null
+++ b/app/src/main/res/layout/fragment_about.xml
@@ -0,0 +1,168 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
index 32933cc59..e707695fd 100644
--- a/app/src/main/res/layout/fragment_settings.xml
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -8,10 +8,11 @@
android:orientation="vertical">
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 45e3c9be3..72a6baff7 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -34,6 +34,8 @@
70dp
36dp
+ 60dp
+
10sp
2sp
diff --git a/app/src/main/res/values/do_not_translate.xml b/app/src/main/res/values/do_not_translate.xml
new file mode 100644
index 000000000..f8724e5e5
--- /dev/null
+++ b/app/src/main/res/values/do_not_translate.xml
@@ -0,0 +1,4 @@
+
+
+ OxygenCobalt
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e2b580c8c..2e8fbe8f9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,6 +1,7 @@
Auxio
+ A sensible and customizable music player for android.
Retry
@@ -36,6 +37,12 @@
Music Playback
The music playback service for Auxio.
+ About
+ Version
+ View on Github
+ Licenses
+ FAQ
+
Settings
Appearance
@@ -94,6 +101,7 @@
No music found.
Music loading failed.
Permissions to read storage are needed.
+ Could not open link.
Search Library…
@@ -116,6 +124,8 @@
Turn shuffle on
Turn shuffle off
Change Repeat Mode
+ Auxio icon
+ Code
Unknown Genre
@@ -151,6 +161,8 @@
%1$s, %2$s
Next From: %s
<b>%1$s</b>: %2$s
+ Songs Loaded: %s
+ Created by %s
- %s Song
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 268458fa7..69ba193bc 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -167,4 +167,10 @@
- @dimen/padding_medium
- @drawable/ui_small_unbounded_ripple
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 764ebecb2..c6aac5674 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = "1.4.20"
+ ext.kotlin_version = "1.4.21"
repositories {
google()