diff --git a/app/src/main/java/zechs/drive/stream/data/model/LatestRelease.kt b/app/src/main/java/zechs/drive/stream/data/model/LatestRelease.kt index 00c5694..94937ea 100644 --- a/app/src/main/java/zechs/drive/stream/data/model/LatestRelease.kt +++ b/app/src/main/java/zechs/drive/stream/data/model/LatestRelease.kt @@ -13,6 +13,6 @@ data class LatestRelease( val htmlUrl: String ) { - fun isLatest() = tagName != BuildConfig.VERSION_NAME + fun isLatest() = tagName == BuildConfig.VERSION_NAME } \ No newline at end of file diff --git a/app/src/main/java/zechs/drive/stream/di/AppModule.kt b/app/src/main/java/zechs/drive/stream/di/AppModule.kt index 28c3756..c0dd0e8 100755 --- a/app/src/main/java/zechs/drive/stream/di/AppModule.kt +++ b/app/src/main/java/zechs/drive/stream/di/AppModule.kt @@ -8,7 +8,7 @@ import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import zechs.drive.stream.utils.SessionManager -import zechs.drive.stream.utils.ThemeManager +import zechs.drive.stream.utils.AppSettings import javax.inject.Singleton @@ -33,6 +33,6 @@ object AppModule { @Provides fun provideThemeDataStore( @ApplicationContext appContext: Context - ): ThemeManager = ThemeManager(appContext) + ): AppSettings = AppSettings(appContext) } \ No newline at end of file diff --git a/app/src/main/java/zechs/drive/stream/ui/BaseFragment.kt b/app/src/main/java/zechs/drive/stream/ui/BaseFragment.kt index 1ec0e0e..bcacfc1 100755 --- a/app/src/main/java/zechs/drive/stream/ui/BaseFragment.kt +++ b/app/src/main/java/zechs/drive/stream/ui/BaseFragment.kt @@ -15,6 +15,14 @@ abstract class BaseFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enterTransition = MaterialSharedAxis( + /* axis */ MaterialSharedAxis.X, + /* forward */ true + ).apply { + interpolator = LinearInterpolator() + duration = 300 + } + returnTransition = MaterialSharedAxis( /* axis */ MaterialSharedAxis.X, /* forward */ false diff --git a/app/src/main/java/zechs/drive/stream/ui/files/FilesFragment.kt b/app/src/main/java/zechs/drive/stream/ui/files/FilesFragment.kt index 012d46e..b886cb0 100644 --- a/app/src/main/java/zechs/drive/stream/ui/files/FilesFragment.kt +++ b/app/src/main/java/zechs/drive/stream/ui/files/FilesFragment.kt @@ -11,6 +11,7 @@ import android.widget.TextView import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible +import androidx.fragment.app.activityViewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -23,7 +24,6 @@ import androidx.recyclerview.widget.RecyclerView import androidx.transition.Transition import androidx.transition.TransitionManager import com.google.android.material.color.MaterialColors -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.google.android.material.transition.MaterialFadeThrough import dagger.hilt.android.AndroidEntryPoint @@ -34,8 +34,10 @@ import zechs.drive.stream.databinding.FragmentFilesBinding import zechs.drive.stream.ui.BaseFragment import zechs.drive.stream.ui.files.adapter.FilesAdapter import zechs.drive.stream.ui.files.adapter.FilesDataModel +import zechs.drive.stream.ui.main.MainViewModel import zechs.drive.stream.ui.player.PlayerActivity import zechs.drive.stream.ui.player2.MPVActivity +import zechs.drive.stream.utils.VideoPlayer import zechs.drive.stream.utils.state.Resource @@ -49,6 +51,7 @@ class FilesFragment : BaseFragment() { private var _binding: FragmentFilesBinding? = null private val binding get() = _binding!! + private val mainViewModel by activityViewModels() private val viewModel by lazy { ViewModelProvider(this)[FilesViewModel::class.java] } @@ -244,18 +247,10 @@ class FilesFragment : BaseFragment() { } private fun launchVideoPlayer(file: DriveFile) { - - val items = arrayOf("ExoPlayer", "MPV") - - MaterialAlertDialogBuilder(requireContext()) - .setTitle(getString(R.string.play_using)) - .setItems(items) { dialog, which -> - when (which) { - 0 -> launchExo(file) - 1 -> viewModel.fetchToken(file) - } - dialog.dismiss() - }.show() + when (mainViewModel.currentPlayerIndex) { + VideoPlayer.EXO_PLAYER -> launchExo(file) + VideoPlayer.MPV -> viewModel.fetchToken(file) + } } private fun mpvObserver() { diff --git a/app/src/main/java/zechs/drive/stream/ui/home/HomeFragment.kt b/app/src/main/java/zechs/drive/stream/ui/home/HomeFragment.kt index 5b0c264..8273462 100644 --- a/app/src/main/java/zechs/drive/stream/ui/home/HomeFragment.kt +++ b/app/src/main/java/zechs/drive/stream/ui/home/HomeFragment.kt @@ -17,8 +17,6 @@ import kotlinx.coroutines.launch import zechs.drive.stream.R import zechs.drive.stream.databinding.FragmentHomeBinding import zechs.drive.stream.ui.BaseFragment -import zechs.drive.stream.ui.main.MainViewModel -import zechs.drive.stream.utils.AppTheme import zechs.drive.stream.utils.ext.navigateSafe @@ -32,7 +30,6 @@ class HomeFragment : BaseFragment() { private val binding get() = _binding!! private val viewModel by activityViewModels() - private val mainViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, @@ -86,6 +83,10 @@ class HomeFragment : BaseFragment() { query = "'root' in parents and trashed=true" ) + btnAppSettings.setOnClickListener { + findNavController().navigateSafe(R.id.action_homeFragment_to_settingsFragment) + } + } setupToolbar() @@ -106,11 +107,6 @@ class HomeFragment : BaseFragment() { } private fun setupToolbar() { - val themes = listOf( - getString(R.string.theme_dark), - getString(R.string.theme_light), - getString(R.string.theme_system) - ) binding.toolbar.setOnMenuItemClickListener { item -> when (item.itemId) { R.id.action_logOut -> { @@ -127,25 +123,7 @@ class HomeFragment : BaseFragment() { .show() return@setOnMenuItemClickListener true } - R.id.action_theme -> { - MaterialAlertDialogBuilder(requireContext()).apply { - setTitle(getString(R.string.select_theme)) - setSingleChoiceItems( - themes.toTypedArray(), - mainViewModel.currentThemeIndex - ) { dialog, item -> - val theme = when (item) { - AppTheme.DARK.value -> AppTheme.DARK - AppTheme.LIGHT.value -> AppTheme.LIGHT - AppTheme.SYSTEM.value -> AppTheme.SYSTEM - else -> throw IllegalArgumentException("Unknown theme value") - } - mainViewModel.setTheme(theme) - dialog.dismiss() - } - }.also { it.show() } - return@setOnMenuItemClickListener true - } + else -> { return@setOnMenuItemClickListener false } diff --git a/app/src/main/java/zechs/drive/stream/ui/main/MainActivity.kt b/app/src/main/java/zechs/drive/stream/ui/main/MainActivity.kt index 25965f1..7e9b3c3 100644 --- a/app/src/main/java/zechs/drive/stream/ui/main/MainActivity.kt +++ b/app/src/main/java/zechs/drive/stream/ui/main/MainActivity.kt @@ -134,10 +134,10 @@ class MainActivity : AppCompatActivity() { is Resource.Success -> { val release = it.data!! if (release.isLatest()) { + Log.d(TAG, "Already on latest version") + } else { Log.d(TAG, "Newer version of app is available (latest=${release.tagName})") sendUpdateNotification(release) - } else { - Log.d(TAG, "Already on latest version") } } is Resource.Error -> Log.d(TAG, it.message!!) diff --git a/app/src/main/java/zechs/drive/stream/ui/main/MainViewModel.kt b/app/src/main/java/zechs/drive/stream/ui/main/MainViewModel.kt index 27385b5..09c8d75 100755 --- a/app/src/main/java/zechs/drive/stream/ui/main/MainViewModel.kt +++ b/app/src/main/java/zechs/drive/stream/ui/main/MainViewModel.kt @@ -13,9 +13,10 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import zechs.drive.stream.data.model.LatestRelease import zechs.drive.stream.data.repository.GithubRepository +import zechs.drive.stream.utils.AppSettings import zechs.drive.stream.utils.AppTheme import zechs.drive.stream.utils.SessionManager -import zechs.drive.stream.utils.ThemeManager +import zechs.drive.stream.utils.VideoPlayer import zechs.drive.stream.utils.state.Resource import javax.inject.Inject @@ -23,7 +24,7 @@ import javax.inject.Inject class MainViewModel @Inject constructor( private val sessionManager: SessionManager, private val githubRepository: GithubRepository, - private val themeManager: ThemeManager + private val appSettings: AppSettings ) : ViewModel() { private val _isLoading = MutableStateFlow(true) @@ -42,10 +43,20 @@ class MainViewModel @Inject constructor( var currentThemeIndex = 2 private set + private val _lastUpdated = MutableStateFlow(null) + val lastUpdated = _lastUpdated.asStateFlow() + + var isChecking = false + private set + init { viewModelScope.launch { getTheme() val status = getLoginStatus() + if (status) { + getPlayer() + getLastUpdated() + } _hasLoggedIn.value = status delay(250L) _isLoading.value = false @@ -59,19 +70,41 @@ class MainViewModel @Inject constructor( return true } - private fun getLatestRelease() = viewModelScope.launch { + fun getLatestRelease() = viewModelScope.launch { + _latest.postValue(Resource.Loading()) + isChecking = true _latest.postValue(githubRepository.getLatestRelease()) + isChecking = false + appSettings.saveLastUpdated() + getLastUpdated() + } + + private fun getLastUpdated() = viewModelScope.launch { + _lastUpdated.emit(appSettings.fetchLastUpdated()) } private suspend fun getTheme() { - val fetchTheme = themeManager.fetchTheme() + val fetchTheme = appSettings.fetchTheme() currentThemeIndex = fetchTheme.value _theme.emit(fetchTheme) } fun setTheme(theme: AppTheme) = viewModelScope.launch { - themeManager.saveTheme(theme) + appSettings.saveTheme(theme) getTheme() } + var currentPlayerIndex = VideoPlayer.MPV + private set + + private suspend fun getPlayer() { + val fetchPlayer = appSettings.fetchPlayer() + currentPlayerIndex = fetchPlayer + } + + fun setPlayer(player: VideoPlayer) = viewModelScope.launch { + appSettings.savePlayer(player) + getPlayer() + } + } diff --git a/app/src/main/java/zechs/drive/stream/ui/settings/SettingsFragment.kt b/app/src/main/java/zechs/drive/stream/ui/settings/SettingsFragment.kt new file mode 100644 index 0000000..8e3d233 --- /dev/null +++ b/app/src/main/java/zechs/drive/stream/ui/settings/SettingsFragment.kt @@ -0,0 +1,173 @@ +package zechs.drive.stream.ui.settings + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isInvisible +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.findNavController +import androidx.transition.TransitionManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.snackbar.Snackbar +import kotlinx.coroutines.launch +import zechs.drive.stream.R +import zechs.drive.stream.databinding.FragmentSettingsBinding +import zechs.drive.stream.ui.BaseFragment +import zechs.drive.stream.ui.main.MainViewModel +import zechs.drive.stream.utils.AppTheme +import zechs.drive.stream.utils.VideoPlayer +import zechs.drive.stream.utils.state.Resource + + +class SettingsFragment : BaseFragment() { + + companion object { + const val TAG = "SettingsFragment" + } + + private var _binding: FragmentSettingsBinding? = null + private val binding get() = _binding!! + + private val mainViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentSettingsBinding.inflate( + inflater, container, /* attachToParent */false + ) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + _binding = FragmentSettingsBinding.bind(view) + + binding.toolbar.setNavigationOnClickListener { + findNavController().navigateUp() + } + + setupThemeMenu() + setupDefaultPlayerMenu() + setupCheckForUpdates() + } + + private fun setupThemeMenu() { + val themes = listOf( + getString(R.string.theme_dark), + getString(R.string.theme_light), + getString(R.string.theme_system) + ) + binding.settingSelectTheme.setOnClickListener { + MaterialAlertDialogBuilder(requireContext()).apply { + setTitle(getString(R.string.select_theme)) + setSingleChoiceItems( + themes.toTypedArray(), + mainViewModel.currentThemeIndex + ) { dialog, item -> + val theme = when (item) { + AppTheme.DARK.value -> AppTheme.DARK + AppTheme.LIGHT.value -> AppTheme.LIGHT + AppTheme.SYSTEM.value -> AppTheme.SYSTEM + else -> throw IllegalArgumentException("Unknown theme value") + } + mainViewModel.setTheme(theme) + dialog.dismiss() + } + }.also { it.show() } + } + } + + private fun setupDefaultPlayerMenu() { + val players = listOf( + getString(R.string.exoplayer), + getString(R.string.mpv) + ) + binding.settingDefaultPlayer.setOnClickListener { + MaterialAlertDialogBuilder(requireContext()).apply { + setTitle(getString(R.string.default_player)) + setSingleChoiceItems( + players.toTypedArray(), + mainViewModel.currentPlayerIndex.value + ) { dialog, item -> + val player = when (item) { + VideoPlayer.EXO_PLAYER.value -> VideoPlayer.EXO_PLAYER + VideoPlayer.MPV.value -> VideoPlayer.MPV + else -> throw IllegalArgumentException("Unknown default player") + } + mainViewModel.setPlayer(player) + dialog.dismiss() + } + }.also { it.show() } + } + } + + private fun setupCheckForUpdates() { + + var isUserClick = false + binding.settingCheckForUpdate.setOnClickListener { + if (!mainViewModel.isChecking) { + mainViewModel.getLatestRelease() + isUserClick = true + } + } + + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + mainViewModel.lastUpdated.collect { + if (it != null) { + val last = "Last checked: $it" + TransitionManager.beginDelayedTransition( + binding.settingCheckForUpdate, + ) + binding.lastCheckedLabel.text = last + } + } + } + } + + fun isChecking(bool: Boolean) { + binding.progressBarChecking.isInvisible = !bool + } + + mainViewModel.latest.observe(viewLifecycleOwner) { state -> + when (state) { + is Resource.Loading -> isChecking(true) + + is Resource.Error -> { + isChecking(false) + showSnackBar("Unable to check for updates") + } + + is Resource.Success -> { + isChecking(false) + val release = state.data!! + if (release.isLatest() && isUserClick) { + showSnackBar("You are already on the latest version") + } + } + } + } + + } + + private fun showSnackBar(message: String) { + Snackbar.make( + binding.root, + message, + Snackbar.LENGTH_SHORT + ).show() + } + + override fun onDestroy() { + super.onDestroy() + _binding = null + } + +} \ No newline at end of file diff --git a/app/src/main/java/zechs/drive/stream/utils/AppSettings.kt b/app/src/main/java/zechs/drive/stream/utils/AppSettings.kt new file mode 100644 index 0000000..88e23af --- /dev/null +++ b/app/src/main/java/zechs/drive/stream/utils/AppSettings.kt @@ -0,0 +1,105 @@ +package zechs.drive.stream.utils + +import android.content.Context +import android.util.Log +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.first +import zechs.drive.stream.utils.util.Converter +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AppSettings @Inject constructor( + @ApplicationContext appContext: Context +) { + + companion object { + private val Context.dataStore by preferencesDataStore( + "APP_SETTINGS" + ) + const val TAG = "AppSettings" + const val APP_THEME = "APP_THEME" + const val VIDEO_PLAYER = "VIDEO_PLAYER" + const val LAST_UPDATED = "LAST_UPDATED" + } + + private val sessionStore = appContext.dataStore + + suspend fun saveTheme(theme: AppTheme) { + val dataStoreKey = stringPreferencesKey(APP_THEME) + sessionStore.edit { settings -> + settings[dataStoreKey] = theme.text + } + Log.d(TAG, "saveTheme: ${theme.text}") + } + + suspend fun fetchTheme(): AppTheme { + val dataStoreKey = stringPreferencesKey(APP_THEME) + val preferences = sessionStore.data.first() + val appTheme = when (preferences[dataStoreKey]) { + AppTheme.LIGHT.text -> AppTheme.LIGHT + AppTheme.DARK.text -> AppTheme.DARK + else -> AppTheme.SYSTEM + } + Log.d(TAG, "fetchTheme: $appTheme") + return appTheme + } + + suspend fun savePlayer(player: VideoPlayer) { + val dataStoreKey = stringPreferencesKey(VIDEO_PLAYER) + sessionStore.edit { settings -> + settings[dataStoreKey] = player.text + } + Log.d(TAG, "savePlayer: ${player.text}") + } + + suspend fun fetchPlayer(): VideoPlayer { + val dataStoreKey = stringPreferencesKey(VIDEO_PLAYER) + val preferences = sessionStore.data.first() + val videoPlayer = when (preferences[dataStoreKey]) { + VideoPlayer.EXO_PLAYER.text -> VideoPlayer.EXO_PLAYER + else -> VideoPlayer.MPV + } + Log.d(TAG, "fetchPlayer: $videoPlayer") + return videoPlayer + } + + suspend fun saveLastUpdated() { + val dataStoreKey = stringPreferencesKey(LAST_UPDATED) + val lastUpdated = System.currentTimeMillis() + sessionStore.edit { settings -> + settings[dataStoreKey] = lastUpdated.toString() + } + Log.d(TAG, "saveLastUpdated: ${Converter.fromTimeInMills(lastUpdated)}") + } + + suspend fun fetchLastUpdated(): String? { + val dataStoreKey = stringPreferencesKey(LAST_UPDATED) + val preferences = sessionStore.data.first() + val lastUpdated = preferences[dataStoreKey] ?: return null + val parsed = Converter.fromTimeInMills(lastUpdated.toLong()) + Log.d(TAG, "fetchLastUpdated: $parsed") + return parsed + } +} + +enum class AppTheme( + val text: String, + val value: Int +) { + DARK("Dark", 0), + LIGHT("Light", 1), + SYSTEM("System", 2) +} + +enum class VideoPlayer( + val text: String, + val value: Int +) { + EXO_PLAYER("ExoPlayer", 0), + MPV("MPV", 1), + +} \ No newline at end of file diff --git a/app/src/main/java/zechs/drive/stream/utils/ThemeManager.kt b/app/src/main/java/zechs/drive/stream/utils/ThemeManager.kt deleted file mode 100644 index ae610ed..0000000 --- a/app/src/main/java/zechs/drive/stream/utils/ThemeManager.kt +++ /dev/null @@ -1,57 +0,0 @@ -package zechs.drive.stream.utils - -import android.content.Context -import android.util.Log -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.stringPreferencesKey -import androidx.datastore.preferences.preferencesDataStore -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.flow.first -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ThemeManager @Inject constructor( - @ApplicationContext appContext: Context -) { - - companion object { - private val Context.dataStore by preferencesDataStore( - "APP_THEME" - ) - const val TAG = "ThemeManager" - const val APP_THEME = "APP_THEME" - } - - private val sessionStore = appContext.dataStore - - suspend fun saveTheme(theme: AppTheme) { - val dataStoreKey = stringPreferencesKey(APP_THEME) - sessionStore.edit { settings -> - settings[dataStoreKey] = theme.text - } - Log.d(TAG, "saveTheme: ${theme.text}") - } - - suspend fun fetchTheme(): AppTheme { - val dataStoreKey = stringPreferencesKey(APP_THEME) - val preferences = sessionStore.data.first() - val appTheme = when (preferences[dataStoreKey]) { - AppTheme.LIGHT.text -> AppTheme.LIGHT - AppTheme.DARK.text -> AppTheme.DARK - else -> AppTheme.SYSTEM - } - Log.d(TAG, "fetchTheme: $appTheme") - return appTheme - } - -} - -enum class AppTheme( - val text: String, - val value: Int -) { - DARK("Dark", 0), - LIGHT("Light", 1), - SYSTEM("System", 2) -} \ No newline at end of file diff --git a/app/src/main/java/zechs/drive/stream/utils/util/Converter.kt b/app/src/main/java/zechs/drive/stream/utils/util/Converter.kt index 4bf9dcf..e68c142 100755 --- a/app/src/main/java/zechs/drive/stream/utils/util/Converter.kt +++ b/app/src/main/java/zechs/drive/stream/utils/util/Converter.kt @@ -1,5 +1,8 @@ package zechs.drive.stream.utils.util +import java.text.SimpleDateFormat +import java.util.Locale + object Converter { fun toHumanSize(size: Long): String { @@ -16,4 +19,13 @@ object Converter { } } + fun fromTimeInMills( + time: Long, + format: String = "hh:mm a dd MMM, yyyy", + ): String { + val date = java.util.Date(time) + val formatter = SimpleDateFormat(format, Locale.ENGLISH) + return formatter.format(date) + } + } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_player_24.xml b/app/src/main/res/drawable/ic_player_24.xml new file mode 100644 index 0000000..57df50b --- /dev/null +++ b/app/src/main/res/drawable/ic_player_24.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_settings_24.xml b/app/src/main/res/drawable/ic_settings_24.xml new file mode 100644 index 0000000..61d7553 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 7feac63..ac841b5 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -61,6 +61,12 @@ android:text="@string/trashed" app:icon="@drawable/ic_delete_24" /> + + diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml new file mode 100644 index 0000000..3207031 --- /dev/null +++ b/app/src/main/res/layout/fragment_settings.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index fcfbcd4..718db52 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -2,12 +2,6 @@ - - + + \ 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 4ae0c49..cdceb6f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -60,4 +60,11 @@ Please fill all the fields Please grant notification permission to receive update notifications Grant + App Settings + Light, Dark and Follow System + Default player + Select default video player + Check for updates + ExoPlayer + MPV \ No newline at end of file