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()