diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1364d482..76259c27 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,8 +20,8 @@ android { applicationId = "com.skyd.anivu" minSdk = 24 targetSdk = 34 - versionCode = 10 - versionName = "1.1-beta07" + versionCode = 11 + versionName = "1.1-beta08" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -100,9 +100,13 @@ android { jvmTarget = "17" } buildFeatures { + compose = true viewBinding = true buildConfig = true } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.11" + } packaging { resources.excludes += mutableSetOf( "DebugProbesKt.bin", @@ -118,7 +122,14 @@ android { tasks.withType(KotlinCompile::class.java).configureEach { kotlinOptions { freeCompilerArgs += listOf( + "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", + "-opt-in=androidx.compose.material.ExperimentalMaterialApi", + "-opt-in=androidx.compose.animation.ExperimentalAnimationApi", + "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi", "-opt-in=coil.annotation.ExperimentalCoilApi", + "-opt-in=androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi", + "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi", + "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi", "-opt-in=kotlinx.coroutines.FlowPreview", "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", @@ -133,6 +144,10 @@ dependencies { implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.navigation:navigation-fragment-ktx:2.7.7") implementation("androidx.navigation:navigation-ui-ktx:2.7.7") + implementation("androidx.compose.ui:ui:1.6.5") + implementation("androidx.compose.material3:material3:1.2.1") + implementation("androidx.compose.material3:material3-window-size-class:1.2.1") + implementation("com.materialkolor:material-kolor:1.4.4") implementation("androidx.room:room-runtime:2.6.1") implementation("androidx.room:room-ktx:2.6.1") implementation("androidx.room:room-paging:2.6.1") diff --git a/app/src/main/java/com/skyd/anivu/base/BaseComposeFragment.kt b/app/src/main/java/com/skyd/anivu/base/BaseComposeFragment.kt new file mode 100644 index 00000000..d4557168 --- /dev/null +++ b/app/src/main/java/com/skyd/anivu/base/BaseComposeFragment.kt @@ -0,0 +1,32 @@ +package com.skyd.anivu.base + +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment +import androidx.navigation.NavHostController +import com.skyd.anivu.ext.findMainNavController +import com.skyd.anivu.model.preference.SettingsProvider +import com.skyd.anivu.ui.local.LocalDarkMode +import com.skyd.anivu.ui.local.LocalNavController +import com.skyd.anivu.ui.local.LocalWindowSizeClass +import com.skyd.anivu.ui.theme.AniVuTheme + + +abstract class BaseComposeFragment : Fragment() { + private lateinit var navController: NavHostController + fun setContentBase(content: @Composable () -> Unit): ComposeView { + navController = findMainNavController() as NavHostController + return ComposeView(requireContext()).apply { + setContent { + CompositionLocalProvider( + LocalNavController provides navController, + LocalWindowSizeClass provides calculateWindowSizeClass(requireActivity()) + ) { + SettingsProvider { AniVuTheme(darkTheme = LocalDarkMode.current, content) } + } + } + } + } +} diff --git a/app/src/main/java/com/skyd/anivu/ext/PaddingValuesExt.kt b/app/src/main/java/com/skyd/anivu/ext/PaddingValuesExt.kt new file mode 100644 index 00000000..409833c6 --- /dev/null +++ b/app/src/main/java/com/skyd/anivu/ext/PaddingValuesExt.kt @@ -0,0 +1,17 @@ +package com.skyd.anivu.ext + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalLayoutDirection + +@Composable +operator fun PaddingValues.plus(other: PaddingValues): PaddingValues = PaddingValues( + top = calculateTopPadding() + other.calculateTopPadding(), + bottom = calculateBottomPadding() + other.calculateBottomPadding(), + start = calculateStartPadding(LocalLayoutDirection.current) + + other.calculateStartPadding(LocalLayoutDirection.current), + end = calculateEndPadding(LocalLayoutDirection.current) + + other.calculateEndPadding(LocalLayoutDirection.current) +) diff --git a/app/src/main/java/com/skyd/anivu/ext/PreferenceExt.kt b/app/src/main/java/com/skyd/anivu/ext/PreferenceExt.kt new file mode 100644 index 00000000..3f57e2bc --- /dev/null +++ b/app/src/main/java/com/skyd/anivu/ext/PreferenceExt.kt @@ -0,0 +1,14 @@ +package com.skyd.anivu.ext + +import androidx.datastore.preferences.core.Preferences +import com.skyd.anivu.model.preference.Settings +import com.skyd.anivu.model.preference.appearance.DarkModePreference +import com.skyd.anivu.model.preference.appearance.ThemePreference + +fun Preferences.toSettings(): Settings { + return Settings( + // Theme + theme = ThemePreference.fromPreferences(this), + darkMode = DarkModePreference.fromPreferences(this), + ) +} diff --git a/app/src/main/java/com/skyd/anivu/model/preference/Settings.kt b/app/src/main/java/com/skyd/anivu/model/preference/Settings.kt new file mode 100644 index 00000000..816762c7 --- /dev/null +++ b/app/src/main/java/com/skyd/anivu/model/preference/Settings.kt @@ -0,0 +1,39 @@ +package com.skyd.anivu.model.preference + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import com.skyd.anivu.ext.dataStore +import com.skyd.anivu.ext.toSettings +import com.skyd.anivu.model.preference.appearance.DarkModePreference +import com.skyd.anivu.model.preference.appearance.ThemePreference +import com.skyd.anivu.ui.local.LocalDarkMode +import com.skyd.anivu.ui.local.LocalTheme +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.map + +data class Settings( + // Theme + val theme: String = ThemePreference.default, + val darkMode: Int = DarkModePreference.default, +) + +@Composable +fun SettingsProvider( + content: @Composable () -> Unit, +) { + val context = LocalContext.current + val settings by remember { context.dataStore.data.map { it.toSettings() } } + .collectAsState(initial = Settings(), context = Dispatchers.Default) + + CompositionLocalProvider( + // Theme + LocalTheme provides settings.theme, + LocalDarkMode provides settings.darkMode, + ) { + content() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/model/preference/appearance/DarkModePreference.kt b/app/src/main/java/com/skyd/anivu/model/preference/appearance/DarkModePreference.kt index b7663f3f..7b6f05a7 100644 --- a/app/src/main/java/com/skyd/anivu/model/preference/appearance/DarkModePreference.kt +++ b/app/src/main/java/com/skyd/anivu/model/preference/appearance/DarkModePreference.kt @@ -3,6 +3,9 @@ package com.skyd.anivu.model.preference.appearance import android.content.Context import android.os.Build import androidx.appcompat.app.AppCompatDelegate +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.intPreferencesKey import com.skyd.anivu.R @@ -66,4 +69,12 @@ object DarkModePreference : BasePreference { } return value } + + @Composable + @ReadOnlyComposable + fun inDark(value: Int) = when (value) { + AppCompatDelegate.MODE_NIGHT_YES -> true + AppCompatDelegate.MODE_NIGHT_NO -> false + else -> isSystemInDarkTheme() + } } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/model/preference/appearance/ThemePreference.kt b/app/src/main/java/com/skyd/anivu/model/preference/appearance/ThemePreference.kt index 508d3b6e..4160e604 100644 --- a/app/src/main/java/com/skyd/anivu/model/preference/appearance/ThemePreference.kt +++ b/app/src/main/java/com/skyd/anivu/model/preference/appearance/ThemePreference.kt @@ -1,6 +1,7 @@ package com.skyd.anivu.model.preference.appearance import android.content.Context +import androidx.compose.ui.graphics.Color import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.stringPreferencesKey import com.google.android.material.color.DynamicColors @@ -77,4 +78,16 @@ object ThemePreference : BasePreference { PURPLE -> R.style.Theme_AniVu_Purple else -> R.style.Theme_AniVu_Pink } + + fun toSeedColor( + context: Context, + value: String = context.dataStore.getOrDefault(this), + ): Color = when (value) { + PINK -> Color(0xFF884A69) + GREEN -> Color(0xFF406836) + BLUE -> Color(0xFF3A608F) + YELLOW -> Color(0xFF6C5E10) + PURPLE -> Color(0xFF65558F) + else -> Color(0xFF884A69) + } } diff --git a/app/src/main/java/com/skyd/anivu/ui/adapter/variety/ViewHolder.kt b/app/src/main/java/com/skyd/anivu/ui/adapter/variety/ViewHolder.kt index 037f7717..dbf2e74c 100644 --- a/app/src/main/java/com/skyd/anivu/ui/adapter/variety/ViewHolder.kt +++ b/app/src/main/java/com/skyd/anivu/ui/adapter/variety/ViewHolder.kt @@ -8,7 +8,6 @@ import com.skyd.anivu.databinding.ItemColorPalette1Binding import com.skyd.anivu.databinding.ItemDownload1Binding import com.skyd.anivu.databinding.ItemEnclosure1Binding import com.skyd.anivu.databinding.ItemFeed1Binding -import com.skyd.anivu.databinding.ItemLicense1Binding import com.skyd.anivu.databinding.ItemLinkEnclosure1Binding import com.skyd.anivu.databinding.ItemMedia1Binding import com.skyd.anivu.databinding.ItemMore1Binding @@ -48,6 +47,3 @@ class ColorPalette1ViewHolder(binding: ItemColorPalette1Binding) : class OtherWorks1ViewHolder(binding: ItemOtherWorks1Binding) : BaseViewHolder(binding) - -class License1ViewHolder(binding: ItemLicense1Binding) : - BaseViewHolder(binding) diff --git a/app/src/main/java/com/skyd/anivu/ui/adapter/variety/proxy/License1Proxy.kt b/app/src/main/java/com/skyd/anivu/ui/adapter/variety/proxy/License1Proxy.kt deleted file mode 100644 index 19af30d1..00000000 --- a/app/src/main/java/com/skyd/anivu/ui/adapter/variety/proxy/License1Proxy.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.skyd.anivu.ui.adapter.variety.proxy - - -import android.view.LayoutInflater -import android.view.ViewGroup -import com.skyd.anivu.databinding.ItemLicense1Binding -import com.skyd.anivu.ext.openBrowser -import com.skyd.anivu.model.bean.LicenseBean -import com.skyd.anivu.ui.adapter.variety.License1ViewHolder -import com.skyd.anivu.ui.adapter.variety.VarietyAdapter - - -class License1Proxy( - private val adapter: VarietyAdapter, -) : - VarietyAdapter.Proxy() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): License1ViewHolder { - val holder = License1ViewHolder( - ItemLicense1Binding - .inflate(LayoutInflater.from(parent.context), parent, false), - ) - holder.itemView.setOnClickListener { - val data = adapter.dataList.getOrNull(holder.bindingAdapterPosition) - if (data !is LicenseBean) return@setOnClickListener - data.link.openBrowser(it.context) - } - return holder - } - - override fun onBindViewHolder( - holder: License1ViewHolder, - data: LicenseBean, - index: Int, - action: ((Any?) -> Unit)? - ) { - holder.binding.apply { - tvLicense1Title.text = data.name - tvLicense1License.text = data.license - tvLicense1Link.text = data.link - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/ui/component/AniVuIconButton.kt b/app/src/main/java/com/skyd/anivu/ui/component/AniVuIconButton.kt new file mode 100644 index 00000000..b1908f59 --- /dev/null +++ b/app/src/main/java/com/skyd/anivu/ui/component/AniVuIconButton.kt @@ -0,0 +1,171 @@ +package com.skyd.anivu.ui.component + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.FilledIconButton +import androidx.compose.material3.FilledTonalIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonColors +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.IconToggleButton +import androidx.compose.material3.IconToggleButtonColors +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.OutlinedIconButton +import androidx.compose.material3.PlainTooltip +import androidx.compose.material3.Text +import androidx.compose.material3.TooltipBox +import androidx.compose.material3.TooltipDefaults +import androidx.compose.material3.rememberTooltipState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter + +enum class AniVuIconButtonStyle { + Normal, Filled, FilledTonal, Outlined +} + +@Composable +fun AniVuIconButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + painter: Painter, + tint: Color? = null, + style: AniVuIconButtonStyle = AniVuIconButtonStyle.Normal, + contentDescription: String? = null, + enabled: Boolean = true, + colors: IconButtonColors? = null, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + val iconButton: @Composable (modifier: Modifier) -> Unit = { + val icon: @Composable () -> Unit = { + Icon( + painter = painter, + tint = tint ?: LocalContentColor.current, + contentDescription = contentDescription, + ) + } + when (style) { + AniVuIconButtonStyle.Normal -> IconButton( + modifier = it, + onClick = onClick, + enabled = enabled, + colors = colors ?: IconButtonDefaults.iconButtonColors(), + interactionSource = interactionSource, + content = icon, + ) + + AniVuIconButtonStyle.Filled -> FilledIconButton( + modifier = it, + onClick = onClick, + enabled = enabled, + colors = colors ?: IconButtonDefaults.filledIconButtonColors(), + interactionSource = interactionSource, + content = icon, + ) + + AniVuIconButtonStyle.FilledTonal -> FilledTonalIconButton( + modifier = it, + onClick = onClick, + enabled = enabled, + colors = colors ?: IconButtonDefaults.filledTonalIconButtonColors(), + interactionSource = interactionSource, + content = icon, + ) + + AniVuIconButtonStyle.Outlined -> OutlinedIconButton( + modifier = it, + onClick = onClick, + enabled = enabled, + colors = colors ?: IconButtonDefaults.outlinedIconButtonColors(), + interactionSource = interactionSource, + content = icon, + ) + } + } + + if (contentDescription.isNullOrEmpty()) { + iconButton(modifier) + } else { + TooltipBox( + modifier = modifier, + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { + PlainTooltip { + Text(contentDescription) + } + }, + state = rememberTooltipState() + ) { + iconButton(Modifier) + } + } +} + +@Composable +fun AniVuIconButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + imageVector: ImageVector, + tint: Color? = null, + style: AniVuIconButtonStyle = AniVuIconButtonStyle.Normal, + contentDescription: String? = null, + enabled: Boolean = true, + colors: IconButtonColors? = null, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + AniVuIconButton( + modifier = modifier, + onClick = onClick, + painter = rememberVectorPainter(image = imageVector), + style = style, + contentDescription = contentDescription, + tint = tint, + enabled = enabled, + colors = colors, + interactionSource = interactionSource, + ) +} + +@Composable +fun RaysIconToggleButton( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + colors: IconToggleButtonColors = IconButtonDefaults.iconToggleButtonColors(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + contentDescription: String? = null, + content: @Composable () -> Unit +) { + val iconButton: @Composable (modifier: Modifier) -> Unit = { + IconToggleButton( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = it, + enabled = enabled, + colors = colors, + interactionSource = interactionSource, + content = content, + ) + } + if (contentDescription.isNullOrEmpty()) { + iconButton(modifier) + } else { + TooltipBox( + modifier = modifier, + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { + PlainTooltip { + Text(contentDescription) + } + }, + state = rememberTooltipState() + ) { + iconButton(Modifier) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/ui/component/AniVuTopBar.kt b/app/src/main/java/com/skyd/anivu/ui/component/AniVuTopBar.kt new file mode 100644 index 00000000..8b179dda --- /dev/null +++ b/app/src/main/java/com/skyd/anivu/ui/component/AniVuTopBar.kt @@ -0,0 +1,133 @@ +package com.skyd.anivu.ui.component + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.ArrowBack +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import com.skyd.anivu.R +import com.skyd.anivu.ext.popBackStackWithLifecycle +import com.skyd.anivu.ui.local.LocalNavController + +enum class AniVuTopBarStyle { + Small, Large, CenterAligned +} + +@Composable +fun AniVuTopBar( + style: AniVuTopBarStyle = AniVuTopBarStyle.Small, + title: @Composable () -> Unit, + contentPadding: @Composable () -> PaddingValues = { PaddingValues() }, + navigationIcon: @Composable () -> Unit = { BackIcon() }, + windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, + actions: @Composable RowScope.() -> Unit = {}, + scrollBehavior: TopAppBarScrollBehavior? = null, +) { + val colors = when (style) { + AniVuTopBarStyle.Small -> TopAppBarDefaults.topAppBarColors() + AniVuTopBarStyle.Large -> TopAppBarDefaults.largeTopAppBarColors() + AniVuTopBarStyle.CenterAligned -> TopAppBarDefaults.centerAlignedTopAppBarColors() + } + val topBarModifier = Modifier.padding(contentPadding()) + when (style) { + AniVuTopBarStyle.Small -> { + TopAppBar( + title = title, + modifier = topBarModifier, + navigationIcon = navigationIcon, + actions = actions, + windowInsets = windowInsets, + colors = colors, + scrollBehavior = scrollBehavior + ) + } + + AniVuTopBarStyle.Large -> { + LargeTopAppBar( + modifier = topBarModifier, + title = title, + navigationIcon = navigationIcon, + actions = actions, + windowInsets = windowInsets, + colors = colors, + scrollBehavior = scrollBehavior + ) + } + + AniVuTopBarStyle.CenterAligned -> { + CenterAlignedTopAppBar( + modifier = topBarModifier, + title = title, + navigationIcon = navigationIcon, + actions = actions, + windowInsets = windowInsets, + colors = colors, + scrollBehavior = scrollBehavior + ) + } + } +} + +@Composable +fun TopBarIcon( + painter: Painter, + modifier: Modifier = Modifier, + onClick: () -> Unit = {}, + tint: Color = LocalContentColor.current, + contentDescription: String?, +) { + AniVuIconButton( + modifier = modifier, + painter = painter, + tint = tint, + contentDescription = contentDescription, + onClick = onClick + ) +} + +@Composable +fun TopBarIcon( + imageVector: ImageVector, + modifier: Modifier = Modifier, + onClick: () -> Unit = {}, + tint: Color = LocalContentColor.current, + contentDescription: String?, +) { + AniVuIconButton( + modifier = modifier, + imageVector = imageVector, + tint = tint, + contentDescription = contentDescription, + onClick = onClick + ) +} + +@Composable +fun BackIcon() { + val navController = LocalNavController.current + BackIcon { + navController.popBackStackWithLifecycle() + } +} + +@Composable +fun BackIcon(onClick: () -> Unit = {}) { + TopBarIcon( + imageVector = Icons.AutoMirrored.Rounded.ArrowBack, + contentDescription = stringResource(id = R.string.back), + onClick = onClick + ) +} diff --git a/app/src/main/java/com/skyd/anivu/ui/fragment/license/LicenseFragment.kt b/app/src/main/java/com/skyd/anivu/ui/fragment/license/LicenseFragment.kt index 888b3853..845881f9 100644 --- a/app/src/main/java/com/skyd/anivu/ui/fragment/license/LicenseFragment.kt +++ b/app/src/main/java/com/skyd/anivu/ui/fragment/license/LicenseFragment.kt @@ -2,56 +2,114 @@ package com.skyd.anivu.ui.fragment.license import android.os.Bundle import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup -import androidx.core.view.ViewCompat -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.GridLayoutManager -import com.skyd.anivu.base.BaseFragment -import com.skyd.anivu.databinding.FragmentLicenseBinding -import com.skyd.anivu.ext.addInsetsByPadding -import com.skyd.anivu.ext.dp -import com.skyd.anivu.ext.popBackStackWithLifecycle +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.skyd.anivu.R +import com.skyd.anivu.base.BaseComposeFragment +import com.skyd.anivu.ext.openBrowser +import com.skyd.anivu.ext.plus import com.skyd.anivu.model.bean.LicenseBean -import com.skyd.anivu.ui.adapter.decoration.AniVuItemDecoration -import com.skyd.anivu.ui.adapter.variety.AniSpanSize -import com.skyd.anivu.ui.adapter.variety.VarietyAdapter -import com.skyd.anivu.ui.adapter.variety.proxy.License1Proxy +import com.skyd.anivu.ui.component.AniVuTopBar +import com.skyd.anivu.ui.component.AniVuTopBarStyle import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint -class LicenseFragment : BaseFragment() { - private val adapter = VarietyAdapter(mutableListOf()).apply { - addProxy(License1Proxy(adapter = this)) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - adapter.dataList = licenseList - } +class LicenseFragment : BaseComposeFragment() { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = setContentBase { LicenseScreen() } +} - override fun FragmentLicenseBinding.initView() { - topAppBar.setNavigationOnClickListener { findNavController().popBackStackWithLifecycle() } - - rvLicenseFragment.layoutManager = GridLayoutManager( - requireContext(), - AniSpanSize.MAX_SPAN_SIZE - ).apply { - spanSizeLookup = AniSpanSize(adapter) +@Composable +fun LicenseScreen() { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + Scaffold( + topBar = { + AniVuTopBar( + style = AniVuTopBarStyle.Large, + title = { Text(text = stringResource(R.string.license_screen_name)) }, + scrollBehavior = scrollBehavior, + ) + } + ) { + val dataList = remember { getLicenseList() } + LazyColumn( + modifier = Modifier + .fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), + contentPadding = PaddingValues(vertical = 7.dp) + it, + ) { + items(items = dataList) { item -> + LicenseItem(item) + } } - rvLicenseFragment.addItemDecoration(AniVuItemDecoration()) - rvLicenseFragment.adapter = adapter } +} - override fun FragmentLicenseBinding.setWindowInsets() { - ablLicenseFragment.addInsetsByPadding(top = true, left = true, right = true) - // Fix: https://github.com/material-components/material-components-android/issues/1310 - ViewCompat.setOnApplyWindowInsetsListener(ctlLicenseFragment, null) - rvLicenseFragment.addInsetsByPadding(bottom = true, left = true, right = true) +@Composable +private fun LicenseItem(data: LicenseBean) { + val context = LocalContext.current + Card( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 7.dp), + shape = RoundedCornerShape(20) + ) { + Column( + modifier = Modifier + .clickable { data.link.openBrowser(context) } + .padding(15.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.weight(1f), + text = data.name, + style = MaterialTheme.typography.titleLarge + ) + Text( + modifier = Modifier.padding(start = 5.dp), + text = data.license, + style = MaterialTheme.typography.labelMedium + ) + } + Text( + modifier = Modifier.padding(top = 6.dp), + text = data.link, + style = MaterialTheme.typography.bodyMedium + ) + } } +} - private val licenseList = mutableListOf( +private fun getLicenseList(): List { + return listOf( LicenseBean( name = "Android Open Source Project", license = "Apache-2.0", @@ -118,7 +176,4 @@ class LicenseFragment : BaseFragment() { link = "https://github.com/aldenml/libtorrent4j", ), ).sortedBy { it.name } - - override fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?) = - FragmentLicenseBinding.inflate(inflater, container, false) } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/ui/local/LocalValue.kt b/app/src/main/java/com/skyd/anivu/ui/local/LocalValue.kt new file mode 100644 index 00000000..6a2c106a --- /dev/null +++ b/app/src/main/java/com/skyd/anivu/ui/local/LocalValue.kt @@ -0,0 +1,18 @@ +package com.skyd.anivu.ui.local + +import androidx.compose.material3.windowsizeclass.WindowSizeClass +import androidx.compose.runtime.compositionLocalOf +import androidx.navigation.NavHostController +import com.skyd.anivu.model.preference.appearance.DarkModePreference +import com.skyd.anivu.model.preference.appearance.ThemePreference + +val LocalNavController = compositionLocalOf { + error("LocalNavController not initialized!") +} + +val LocalWindowSizeClass = compositionLocalOf { + error("LocalWindowSizeClass not initialized!") +} + +val LocalTheme = compositionLocalOf { ThemePreference.default } +val LocalDarkMode = compositionLocalOf { DarkModePreference.default } diff --git a/app/src/main/java/com/skyd/anivu/ui/theme/SystemTonalPalettes.kt b/app/src/main/java/com/skyd/anivu/ui/theme/SystemTonalPalettes.kt new file mode 100644 index 00000000..bc92c503 --- /dev/null +++ b/app/src/main/java/com/skyd/anivu/ui/theme/SystemTonalPalettes.kt @@ -0,0 +1,189 @@ +package com.skyd.anivu.ui.theme + +import android.content.Context +import android.os.Build +import androidx.annotation.ColorRes +import androidx.annotation.RequiresApi +import androidx.compose.material3.ColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import com.materialkolor.Contrast +import com.materialkolor.dynamiccolor.MaterialDynamicColors +import com.materialkolor.ktx.from +import com.materialkolor.ktx.getColor +import com.materialkolor.ktx.toHct +import com.materialkolor.palettes.TonalPalette +import com.materialkolor.scheme.DynamicScheme +import com.materialkolor.scheme.Variant + +private fun getColorFromTheme(context: Context, @ColorRes id: Int): Color { + return Color(context.resources.getColor(id, context.theme)) +} + +@RequiresApi(Build.VERSION_CODES.S) +private fun primarySystem(context: Context, tone: Int = 50): Color = when (tone) { + 0 -> getColorFromTheme(context, android.R.color.system_accent1_1000) + 10 -> getColorFromTheme(context, android.R.color.system_accent1_900) + 20 -> getColorFromTheme(context, android.R.color.system_accent1_800) + 30 -> getColorFromTheme(context, android.R.color.system_accent1_700) + 40 -> getColorFromTheme(context, android.R.color.system_accent1_600) + 50 -> getColorFromTheme(context, android.R.color.system_accent1_500) + 60 -> getColorFromTheme(context, android.R.color.system_accent1_400) + 70 -> getColorFromTheme(context, android.R.color.system_accent1_300) + 80 -> getColorFromTheme(context, android.R.color.system_accent1_200) + 90 -> getColorFromTheme(context, android.R.color.system_accent1_100) + 95 -> getColorFromTheme(context, android.R.color.system_accent1_50) + 99 -> getColorFromTheme(context, android.R.color.system_accent1_10) + 100 -> getColorFromTheme(context, android.R.color.system_accent1_0) + else -> throw IllegalArgumentException("Unknown primary tone: $tone") +} + +@RequiresApi(Build.VERSION_CODES.S) +private fun secondarySystem(context: Context, tone: Int = 50): Color = when (tone) { + 0 -> getColorFromTheme(context, android.R.color.system_accent2_1000) + 10 -> getColorFromTheme(context, android.R.color.system_accent2_900) + 20 -> getColorFromTheme(context, android.R.color.system_accent2_800) + 30 -> getColorFromTheme(context, android.R.color.system_accent2_700) + 40 -> getColorFromTheme(context, android.R.color.system_accent2_600) + 50 -> getColorFromTheme(context, android.R.color.system_accent2_500) + 60 -> getColorFromTheme(context, android.R.color.system_accent2_400) + 70 -> getColorFromTheme(context, android.R.color.system_accent2_300) + 80 -> getColorFromTheme(context, android.R.color.system_accent2_200) + 90 -> getColorFromTheme(context, android.R.color.system_accent2_100) + 95 -> getColorFromTheme(context, android.R.color.system_accent2_50) + 99 -> getColorFromTheme(context, android.R.color.system_accent2_10) + 100 -> getColorFromTheme(context, android.R.color.system_accent2_0) + else -> throw IllegalArgumentException("Unknown secondary tone: $tone") +} + +@RequiresApi(Build.VERSION_CODES.S) +private fun tertiarySystem(context: Context, tone: Int = 50): Color = when (tone) { + 0 -> getColorFromTheme(context, android.R.color.system_accent3_1000) + 10 -> getColorFromTheme(context, android.R.color.system_accent3_900) + 20 -> getColorFromTheme(context, android.R.color.system_accent3_800) + 30 -> getColorFromTheme(context, android.R.color.system_accent3_700) + 40 -> getColorFromTheme(context, android.R.color.system_accent3_600) + 50 -> getColorFromTheme(context, android.R.color.system_accent3_500) + 60 -> getColorFromTheme(context, android.R.color.system_accent3_400) + 70 -> getColorFromTheme(context, android.R.color.system_accent3_300) + 80 -> getColorFromTheme(context, android.R.color.system_accent3_200) + 90 -> getColorFromTheme(context, android.R.color.system_accent3_100) + 95 -> getColorFromTheme(context, android.R.color.system_accent3_50) + 99 -> getColorFromTheme(context, android.R.color.system_accent3_10) + 100 -> getColorFromTheme(context, android.R.color.system_accent3_0) + else -> throw IllegalArgumentException("Unknown tertiary tone: $tone") +} + +@RequiresApi(Build.VERSION_CODES.S) +private fun neutralSystem(context: Context, tone: Int = 50): Color = when (tone) { + 0 -> getColorFromTheme(context, android.R.color.system_neutral1_1000) + 10 -> getColorFromTheme(context, android.R.color.system_neutral1_900) + 20 -> getColorFromTheme(context, android.R.color.system_neutral1_800) + 30 -> getColorFromTheme(context, android.R.color.system_neutral1_700) + 40 -> getColorFromTheme(context, android.R.color.system_neutral1_600) + 50 -> getColorFromTheme(context, android.R.color.system_neutral1_500) + 60 -> getColorFromTheme(context, android.R.color.system_neutral1_400) + 70 -> getColorFromTheme(context, android.R.color.system_neutral1_300) + 80 -> getColorFromTheme(context, android.R.color.system_neutral1_200) + 90 -> getColorFromTheme(context, android.R.color.system_neutral1_100) + 95 -> getColorFromTheme(context, android.R.color.system_neutral1_50) + 99 -> getColorFromTheme(context, android.R.color.system_neutral1_10) + 100 -> getColorFromTheme(context, android.R.color.system_neutral1_0) + else -> throw IllegalArgumentException("Unknown neutral tone: $tone") +} + +@RequiresApi(Build.VERSION_CODES.S) +private fun neutralVariantSystem(context: Context, tone: Int = 50): Color = when (tone) { + 0 -> getColorFromTheme(context, android.R.color.system_neutral2_1000) + 10 -> getColorFromTheme(context, android.R.color.system_neutral2_900) + 20 -> getColorFromTheme(context, android.R.color.system_neutral2_800) + 30 -> getColorFromTheme(context, android.R.color.system_neutral2_700) + 40 -> getColorFromTheme(context, android.R.color.system_neutral2_600) + 50 -> getColorFromTheme(context, android.R.color.system_neutral2_500) + 60 -> getColorFromTheme(context, android.R.color.system_neutral2_400) + 70 -> getColorFromTheme(context, android.R.color.system_neutral2_300) + 80 -> getColorFromTheme(context, android.R.color.system_neutral2_200) + 90 -> getColorFromTheme(context, android.R.color.system_neutral2_100) + 95 -> getColorFromTheme(context, android.R.color.system_neutral2_50) + 99 -> getColorFromTheme(context, android.R.color.system_neutral2_10) + 100 -> getColorFromTheme(context, android.R.color.system_neutral2_0) + else -> throw IllegalArgumentException("Unknown neutral variant tone: $tone") +} + +@RequiresApi(Build.VERSION_CODES.S) +@Composable +fun rememberSystemDynamicColorScheme( + isDark: Boolean, + variant: Variant = Variant.TONAL_SPOT, + contrastLevel: Double = 0.0, + isExtendedFidelity: Boolean = false, +): ColorScheme { + val context = LocalContext.current + return remember(context, isDark, variant, contrastLevel, isExtendedFidelity) { + systemDynamicColorScheme(context, isDark, variant, contrastLevel, isExtendedFidelity) + } +} + +@RequiresApi(Build.VERSION_CODES.S) +fun systemDynamicColorScheme( + context: Context, + isDark: Boolean, + variant: Variant = Variant.TONAL_SPOT, + contrastLevel: Double = Contrast.Default.value, + isExtendedFidelity: Boolean = false, +): ColorScheme { + val primaryColor = primarySystem(context) + val scheme = DynamicScheme( + sourceColorHct = primaryColor.toHct(), + variant = variant, + isDark = isDark, + contrastLevel = contrastLevel, + primaryPalette = TonalPalette.from(primaryColor), + secondaryPalette = TonalPalette.from(secondarySystem(context)), + tertiaryPalette = TonalPalette.from(tertiarySystem(context)), + neutralPalette = TonalPalette.from(neutralSystem(context)), + neutralVariantPalette = TonalPalette.from(neutralVariantSystem(context)), + ) + val colors = MaterialDynamicColors(isExtendedFidelity) + + return ColorScheme( + background = colors.background().getColor(scheme), + error = colors.error().getColor(scheme), + errorContainer = colors.errorContainer().getColor(scheme), + inverseOnSurface = colors.inverseOnSurface().getColor(scheme), + inversePrimary = colors.inversePrimary().getColor(scheme), + inverseSurface = colors.inverseSurface().getColor(scheme), + onBackground = colors.onBackground().getColor(scheme), + onError = colors.onError().getColor(scheme), + onErrorContainer = colors.onErrorContainer().getColor(scheme), + onPrimary = colors.onPrimary().getColor(scheme), + onPrimaryContainer = colors.onPrimaryContainer().getColor(scheme), + onSecondary = colors.onSecondary().getColor(scheme), + onSecondaryContainer = colors.onSecondaryContainer().getColor(scheme), + onSurface = colors.onSurface().getColor(scheme), + onSurfaceVariant = colors.onSurfaceVariant().getColor(scheme), + onTertiary = colors.onTertiary().getColor(scheme), + onTertiaryContainer = colors.onTertiaryContainer().getColor(scheme), + outline = colors.outline().getColor(scheme), + outlineVariant = colors.outlineVariant().getColor(scheme), + primary = colors.primary().getColor(scheme), + primaryContainer = colors.primaryContainer().getColor(scheme), + scrim = colors.scrim().getColor(scheme), + secondary = colors.secondary().getColor(scheme), + secondaryContainer = colors.secondaryContainer().getColor(scheme), + surface = colors.surface().getColor(scheme), + surfaceTint = colors.surfaceTint().getColor(scheme), + surfaceBright = colors.surfaceBright().getColor(scheme), + surfaceDim = colors.surfaceDim().getColor(scheme), + surfaceContainer = colors.surfaceContainer().getColor(scheme), + surfaceContainerHigh = colors.surfaceContainerHigh().getColor(scheme), + surfaceContainerHighest = colors.surfaceContainerHighest().getColor(scheme), + surfaceContainerLow = colors.surfaceContainerLow().getColor(scheme), + surfaceContainerLowest = colors.surfaceContainerLowest().getColor(scheme), + surfaceVariant = colors.surfaceVariant().getColor(scheme), + tertiary = colors.tertiary().getColor(scheme), + tertiaryContainer = colors.tertiaryContainer().getColor(scheme), + ) +} diff --git a/app/src/main/java/com/skyd/anivu/ui/theme/Theme.kt b/app/src/main/java/com/skyd/anivu/ui/theme/Theme.kt new file mode 100644 index 00000000..6c874c03 --- /dev/null +++ b/app/src/main/java/com/skyd/anivu/ui/theme/Theme.kt @@ -0,0 +1,130 @@ +package com.skyd.anivu.ui.theme + +import android.app.WallpaperManager +import android.os.Build +import android.view.View +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsControllerCompat +import com.materialkolor.dynamicColorScheme +import com.materialkolor.rememberDynamicColorScheme +import com.skyd.anivu.ext.activity +import com.skyd.anivu.model.preference.appearance.DarkModePreference +import com.skyd.anivu.model.preference.appearance.ThemePreference +import com.skyd.anivu.ui.local.LocalTheme + +@Composable +fun AniVuTheme( + darkTheme: Int, + content: @Composable () -> Unit +) { + AniVuTheme( + darkTheme = DarkModePreference.inDark(darkTheme), + content = content + ) +} + +@Composable +fun AniVuTheme( + darkTheme: Boolean, + wallpaperColors: Map = extractAllColors(darkTheme), + content: @Composable () -> Unit +) { + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + setSystemBarsColor(view, darkTheme) + } + } + + val themeName = LocalTheme.current + val context = LocalContext.current + + MaterialTheme( + colorScheme = remember(themeName) { + wallpaperColors.getOrElse(themeName) { + dynamicColorScheme( + seedColor = ThemePreference.toSeedColor( + context = context, + value = ThemePreference.values[0], + ), + isDark = darkTheme, + ) + } + }, + typography = Typography, + content = content + ) +} + +private fun setSystemBarsColor(view: View, darkMode: Boolean) { + val window = view.context.activity.window + WindowCompat.setDecorFitsSystemWindows(window, false) + window.apply { + statusBarColor = android.graphics.Color.TRANSPARENT + navigationBarColor = android.graphics.Color.TRANSPARENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + navigationBarDividerColor = android.graphics.Color.TRANSPARENT + } + // 状态栏和导航栏字体颜色 + WindowInsetsControllerCompat(this, view).apply { + isAppearanceLightStatusBars = !darkMode + isAppearanceLightNavigationBars = !darkMode + } + } +} + +@Composable +fun extractAllColors(darkTheme: Boolean): Map { + return extractColors(darkTheme) + extractColorsFromWallpaper(darkTheme) +} + +@Composable +fun extractColors(darkTheme: Boolean): Map { + return ThemePreference.values.associateWith { + rememberDynamicColorScheme( + ThemePreference.toSeedColor(LocalContext.current, it), isDark = darkTheme + ) + }.toMutableMap() +} + +@Composable +fun extractColorsFromWallpaper(darkTheme: Boolean): Map { + val context = LocalContext.current + val preset = mutableMapOf() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 && !LocalView.current.isInEditMode) { + val colors = WallpaperManager.getInstance(context) + .getWallpaperColors(WallpaperManager.FLAG_SYSTEM) + val primary = colors?.primaryColor?.toArgb() + val secondary = colors?.secondaryColor?.toArgb() + val tertiary = colors?.tertiaryColor?.toArgb() + if (primary != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + preset["WallpaperPrimary"] = rememberSystemDynamicColorScheme(isDark = darkTheme) + } else { + preset["WallpaperPrimary"] = rememberDynamicColorScheme( + seedColor = Color(primary), isDark = darkTheme, + ) + } + } + if (secondary != null) { + preset["WallpaperSecondary"] = rememberDynamicColorScheme( + seedColor = Color(secondary), isDark = darkTheme, + ) + } + if (tertiary != null) { + preset["WallpaperTertiary"] = rememberDynamicColorScheme( + seedColor = Color(tertiary), isDark = darkTheme, + ) + } + } + return preset +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/ui/theme/Type.kt b/app/src/main/java/com/skyd/anivu/ui/theme/Type.kt new file mode 100644 index 00000000..f4583427 --- /dev/null +++ b/app/src/main/java/com/skyd/anivu/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.skyd.anivu.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override +titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp +), +labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp +) +*/ +) \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_license.xml b/app/src/main/res/layout/fragment_license.xml deleted file mode 100644 index a576dd2c..00000000 --- a/app/src/main/res/layout/fragment_license.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_license_1.xml b/app/src/main/res/layout/item_license_1.xml deleted file mode 100644 index 4a174295..00000000 --- a/app/src/main/res/layout/item_license_1.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/menu_about.xml b/app/src/main/res/menu/menu_about.xml index aa23194a..4b7ad9b4 100644 --- a/app/src/main/res/menu/menu_about.xml +++ b/app/src/main/res/menu/menu_about.xml @@ -5,6 +5,6 @@ \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 1cfcde06..a75b8d72 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -81,8 +81,7 @@ + android:label="@string/license_screen_name" /> diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index fc616d01..a729ca37 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -57,7 +57,7 @@ 一个在本地记录、查找抽象段落/评论区小作文的工具。🤗 您还在为记不住小作文内容,面临前面、中间、后面都忘了的尴尬处境吗?使用这款工具将帮助您记录您所遇到的小作文,再也不因为忘记而烦恼!😋 当您在夜间🌙使用手机时,Night Screen 可以帮助您减少屏幕亮度,减少对眼睛的伤害。 找不到浏览器!网址:%s - 开源许可证 + 开源许可证 搜索 清除 搜索文章 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5116b3f8..aff3ebf3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -62,7 +62,7 @@ Night Screen When you use your phone at night 🌙, Night Screen can help you reduce the brightness of the screen and reduce the damage to your eyes. Can\'t find the browser! Link: %s - License + License Search Clear Search articles