mirror of
https://github.com/IllusionMan1212/NPS-Browser.git
synced 2026-10-24 02:12:37 +00:00
refactor/feat/WIP: completely move to compose, partially finished onboarding screen
This commit is contained in:
@@ -1,59 +0,0 @@
|
||||
package com.illusionware.npsbrowser
|
||||
|
||||
import com.github.wrdlbrnft.sortedlistadapter.SortedListAdapter
|
||||
|
||||
data class AppData(
|
||||
val titleID: String?, val region: String?, val title: String?, val link: String?,
|
||||
val license: String?, val contentID: String?, val lastDateTime: String?,
|
||||
val fileSize: Number?, val sha256: String?, val minFW: String?
|
||||
) : SortedListAdapter.ViewModel {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as AppData
|
||||
|
||||
if (titleID != other.titleID) return false
|
||||
if (region != other.region) return false
|
||||
if (title != other.title) return false
|
||||
if (link != other.link) return false
|
||||
if (license != other.license) return false
|
||||
if (contentID != other.contentID) return false
|
||||
if (lastDateTime != other.lastDateTime) return false
|
||||
if (fileSize != other.fileSize) return false
|
||||
if (sha256 != other.sha256) return false
|
||||
if (minFW != other.minFW) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = titleID?.hashCode() ?: 0
|
||||
result = 31 * result + (region?.hashCode() ?: 0)
|
||||
result = 31 * result + (title?.hashCode() ?: 0)
|
||||
result = 31 * result + (link?.hashCode() ?: 0)
|
||||
result = 31 * result + (license?.hashCode() ?: 0)
|
||||
result = 31 * result + (contentID?.hashCode() ?: 0)
|
||||
result = 31 * result + (lastDateTime?.hashCode() ?: 0)
|
||||
result = 31 * result + (fileSize?.hashCode() ?: 0)
|
||||
result = 31 * result + (sha256?.hashCode() ?: 0)
|
||||
result = 31 * result + (minFW?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
override fun <T : Any> isSameModelAs(model: T): Boolean {
|
||||
if (model is AppData) {
|
||||
val other: AppData = model
|
||||
return "${other.titleID} ${other.title}" == "$titleID $title"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun <T : Any> isContentTheSameAs(model: T): Boolean {
|
||||
if (model is AppData) {
|
||||
val other: AppData = model
|
||||
return title?.equals(other.title) ?: (other.title == null)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.illusionware.npsbrowser
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class Preferences(private val context: Context) {
|
||||
companion object {
|
||||
private val Context.dataStore by preferencesDataStore(name = "settings")
|
||||
private val APP_THEME = intPreferencesKey("app_theme")
|
||||
private val DATA_LAYOUT = intPreferencesKey("layout")
|
||||
private val SEEN_ONBOARDING = booleanPreferencesKey("seen_onboarding")
|
||||
}
|
||||
|
||||
val theme = context.dataStore.data.map { prefs ->
|
||||
prefs[APP_THEME] ?: 2
|
||||
}
|
||||
|
||||
val layout = context.dataStore.data.map{ prefs ->
|
||||
prefs[DATA_LAYOUT] ?: 0
|
||||
}
|
||||
|
||||
val seenOnboarding = context.dataStore.data.map{ prefs ->
|
||||
prefs[SEEN_ONBOARDING] ?: false
|
||||
}
|
||||
|
||||
suspend fun changeTheme(theme: Int) {
|
||||
context.dataStore.edit { prefs ->
|
||||
prefs[APP_THEME] = theme
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun changeLayout(layout: Int) {
|
||||
context.dataStore.edit { prefs ->
|
||||
prefs[DATA_LAYOUT] = layout
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun finishOnboarding() {
|
||||
context.dataStore.edit { prefs ->
|
||||
prefs[SEEN_ONBOARDING] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.illusionware.npsbrowser
|
||||
|
||||
import android.content.Context
|
||||
import android.view.GestureDetector
|
||||
import android.view.GestureDetector.SimpleOnGestureListener
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener
|
||||
|
||||
class RecyclerTouchListener(context: Context?, recyclerView: RecyclerView,
|
||||
private val clickListener: ClickListener?) : OnItemTouchListener {
|
||||
interface ClickListener {
|
||||
fun onClick(view: View?, position: Int)
|
||||
fun onLongClick(view: View?, recyclerView: RecyclerView?, position: Int)
|
||||
}
|
||||
|
||||
private val gestureDetector: GestureDetector
|
||||
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
|
||||
val child = rv.findChildViewUnder(e.x, e.y)
|
||||
if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) {
|
||||
clickListener.onClick(child, rv.getChildAdapterPosition(child))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {}
|
||||
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
|
||||
|
||||
init {
|
||||
gestureDetector = GestureDetector(context, object : SimpleOnGestureListener() {
|
||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onLongPress(e: MotionEvent) {
|
||||
val child = recyclerView.findChildViewUnder(e.x, e.y)
|
||||
if (child != null && clickListener != null) {
|
||||
clickListener.onLongClick(child, recyclerView, recyclerView.getChildAdapterPosition(child))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.illusionware.npsbrowser
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.ListPreference
|
||||
|
||||
class ThemePreference : ListPreference {
|
||||
constructor(context : Context?, attrs : AttributeSet?, defStyleAttr : Int) : super(context!!, attrs, defStyleAttr)
|
||||
|
||||
constructor(context : Context?, attrs : AttributeSet?) : super(context!!, attrs)
|
||||
|
||||
constructor(context : Context?) : super(context!!)
|
||||
|
||||
override fun callChangeListener(newValue : Any?) : Boolean {
|
||||
AppCompatDelegate.setDefaultNightMode(when ((newValue as String).toInt()) {
|
||||
0 -> AppCompatDelegate.MODE_NIGHT_NO
|
||||
1 -> AppCompatDelegate.MODE_NIGHT_YES
|
||||
2 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
|
||||
})
|
||||
|
||||
return super.callChangeListener(newValue)
|
||||
}
|
||||
|
||||
override fun setValue(value: String?) {
|
||||
super.setValue(value)
|
||||
summary = value
|
||||
}
|
||||
|
||||
override fun setSummary(summary: CharSequence?) {
|
||||
super.setSummary(entry)
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.illusionware.npsbrowser.activities
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.illusionware.npsbrowser.R
|
||||
|
||||
open class BaseActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var sharedPreferences : SharedPreferences
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
PreferenceManager.setDefaultValues(this, R.xml.root_preferences, false)
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val decor: View = window.decorView
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(when ((sharedPreferences.getString("app_theme", "2")?.toInt())) {
|
||||
0 -> {
|
||||
decor.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||
AppCompatDelegate.MODE_NIGHT_NO
|
||||
}
|
||||
1 -> {
|
||||
decor.systemUiVisibility = 0;
|
||||
AppCompatDelegate.MODE_NIGHT_YES
|
||||
}
|
||||
2 -> {
|
||||
decor.systemUiVisibility = if (resources.configuration.uiMode and
|
||||
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) 0 else View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
}
|
||||
else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,296 @@
|
||||
package com.illusionware.npsbrowser.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.GridView
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.illusionware.npsbrowser.Preferences
|
||||
import com.illusionware.npsbrowser.R
|
||||
import com.illusionware.npsbrowser.fragments.mainactivity.MainActivityFragment
|
||||
import com.illusionware.npsbrowser.ui.theme.ColorAccent
|
||||
import com.illusionware.npsbrowser.ui.theme.NPSBrowserTheme
|
||||
import com.illusionware.npsbrowser.ui.theme.Typography
|
||||
import com.illusionware.npsbrowser.ui.components.*
|
||||
import com.illusionware.npsbrowser.ui.pages.SettingsScreen
|
||||
import com.illusionware.npsbrowser.ui.pages.OnBoarding
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener {
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
setTheme(R.style.NPSTheme)
|
||||
setContentView(R.layout.activity_main)
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
supportFragmentManager.addOnBackStackChangedListener(this);
|
||||
|
||||
if (supportFragmentManager.backStackEntryCount > 0) {
|
||||
showUpButton()
|
||||
setContent {
|
||||
Router()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Router() {
|
||||
val prefs = Preferences(LocalContext.current)
|
||||
var theme by remember { runBlocking { mutableStateOf(prefs.theme.first()) } }
|
||||
var seenOnboarding by remember { runBlocking { mutableStateOf(prefs.seenOnboarding.first()) } }
|
||||
val navController = rememberNavController()
|
||||
|
||||
val darkTheme = when (theme) {
|
||||
0 -> false
|
||||
1 -> true
|
||||
else -> isSystemInDarkTheme()
|
||||
}
|
||||
|
||||
NPSBrowserTheme(darkTheme = darkTheme) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = if (seenOnboarding) MaterialTheme.colorScheme.background else ColorAccent
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = if (seenOnboarding) "Home" else "Onboarding",
|
||||
) {
|
||||
composable(route = "Home") {
|
||||
HomePage(
|
||||
navigateToSettings = { navController.navigate("Settings") }
|
||||
)
|
||||
}
|
||||
composable(route = "Settings") {
|
||||
SettingsScreen(
|
||||
navigationGoBack = { navController.popBackStack() },
|
||||
onThemeChange = { v -> theme = v },
|
||||
)
|
||||
}
|
||||
composable(route = "Onboarding") {
|
||||
OnBoarding(
|
||||
navigateToHome = {
|
||||
navController.popBackStack()
|
||||
seenOnboarding = true
|
||||
navController.navigate("Home")
|
||||
},
|
||||
darkTheme = darkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@Preview()
|
||||
fun HomePage(navigateToSettings: () -> Unit = {}) {
|
||||
var isSearchOpen by rememberSaveable { mutableStateOf(false)}
|
||||
var searchQuery by rememberSaveable { mutableStateOf("")}
|
||||
val searchBar = remember { FocusRequester() }
|
||||
var layoutDialogOpen by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
if (!isSearchOpen) {
|
||||
Text(text = stringResource(id = R.string.app_name))
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (isSearchOpen) {
|
||||
NPSIconButton(
|
||||
tooltip = "Close Search",
|
||||
onClick = { isSearchOpen = false }
|
||||
){
|
||||
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "Close Search")
|
||||
}
|
||||
Spacer(Modifier.width(12.dp))
|
||||
TextField(
|
||||
value = searchQuery,
|
||||
onValueChange = {str -> searchQuery = str},
|
||||
Modifier
|
||||
.weight(1.0f)
|
||||
.focusRequester(searchBar),
|
||||
placeholder = {
|
||||
Text(text = stringResource(id = R.string.search), style = Typography.bodyLarge)
|
||||
},
|
||||
singleLine = true,
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedContainerColor = Color.Transparent,
|
||||
unfocusedContainerColor = Color.Transparent,
|
||||
disabledContainerColor = Color.Transparent,
|
||||
cursorColor = MaterialTheme.colorScheme.primary,
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
)
|
||||
Spacer(Modifier.width(12.dp))
|
||||
if (searchQuery.isNotEmpty()) {
|
||||
NPSIconButton(
|
||||
tooltip = "Clear Search",
|
||||
onClick = { searchQuery = "" }
|
||||
){
|
||||
Icon(imageVector = Icons.Filled.Close, contentDescription = "Clear Search")
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
searchBar.requestFocus()
|
||||
}
|
||||
}
|
||||
if (!isSearchOpen) {
|
||||
NPSIconButton(
|
||||
tooltip = stringResource(id = R.string.search_tooltip),
|
||||
onClick = { isSearchOpen = true }
|
||||
){
|
||||
Icon(imageVector = Icons.Filled.Search, contentDescription = stringResource(
|
||||
id = R.string.search_tooltip
|
||||
))
|
||||
}
|
||||
}
|
||||
NPSIconButton(
|
||||
tooltip = stringResource(id = R.string.layout),
|
||||
onClick = { layoutDialogOpen = true }
|
||||
) {
|
||||
Icon(imageVector = Icons.Filled.GridView, contentDescription = stringResource(
|
||||
id = R.string.layout
|
||||
))
|
||||
}
|
||||
NPSIconButton(
|
||||
tooltip = stringResource(id = R.string.title_activity_settings),
|
||||
onClick = { navigateToSettings() }
|
||||
){
|
||||
Icon(imageVector = Icons.Filled.Settings, contentDescription = stringResource(
|
||||
id = R.string.title_activity_settings
|
||||
))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
Column(Modifier.padding(padding)) {
|
||||
NoTsvs(navigateToSettings = navigateToSettings)
|
||||
}
|
||||
// TODO: only show NoTsvs when there are no Tsvs loaded
|
||||
|
||||
if (layoutDialogOpen) {
|
||||
LayoutDialog(onDismiss = { layoutDialogOpen = false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun NoTsvs(padding: PaddingValues = PaddingValues(0.dp), navigateToSettings: () -> Unit = {}) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ph_info),
|
||||
contentDescription = "Info",
|
||||
tint = MaterialTheme.colorScheme.outline,
|
||||
modifier = Modifier.size(92.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
Text(
|
||||
text = "You haven't added any TSVs",
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
style = Typography.bodyLarge
|
||||
)
|
||||
Row {
|
||||
Text(
|
||||
text = "Add your TSV files from the ",
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
style = Typography.bodyLarge
|
||||
)
|
||||
Text(
|
||||
text = "settings",
|
||||
color = ColorAccent,
|
||||
style = Typography.bodyLarge,
|
||||
modifier = Modifier.clickable {
|
||||
navigateToSettings()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LayoutDialog(onDismiss: () -> Unit = {}) {
|
||||
val prefs = Preferences(LocalContext.current)
|
||||
val layout = prefs.layout.collectAsState(initial = "")
|
||||
|
||||
NPSAlertDialog(
|
||||
onDismiss = onDismiss,
|
||||
title = stringResource(id = R.string.layout),
|
||||
buttons = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(text = "Cancel")
|
||||
}
|
||||
}
|
||||
) {
|
||||
Column {
|
||||
NPSRadioButton(
|
||||
text = "List",
|
||||
selected = layout.value == 0,
|
||||
onClick = { CoroutineScope(Dispatchers.IO).launch { prefs.changeLayout(0) } },
|
||||
)
|
||||
NPSRadioButton(
|
||||
text = "Grid",
|
||||
selected = layout.value == 1,
|
||||
onClick = { CoroutineScope(Dispatchers.IO).launch { prefs.changeLayout(1) } },
|
||||
)
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, MainActivityFragment())
|
||||
.commitNow()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
supportFragmentManager.popBackStack()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onBackStackChanged() {
|
||||
// enable Up button only if there are entries on the backstack
|
||||
if (supportFragmentManager.backStackEntryCount < 1) {
|
||||
hideUpButton()
|
||||
} else {
|
||||
showUpButton()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showUpButton() {
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
private fun hideUpButton() {
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
package com.illusionware.npsbrowser.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import com.github.wrdlbrnft.modularadapter.ModularAdapter
|
||||
import com.github.wrdlbrnft.sortedlistadapter.SortedListAdapter
|
||||
import com.illusionware.npsbrowser.AppData
|
||||
import com.illusionware.npsbrowser.R
|
||||
import com.illusionware.npsbrowser.activities.MainActivity
|
||||
import com.illusionware.npsbrowser.fragments.SingleAppFragment
|
||||
import com.illusionware.npsbrowser.fragments.mainactivity.MainActivityFragment
|
||||
import java.util.Comparator
|
||||
|
||||
private val comparator: Comparator<AppData> = SortedListAdapter.ComparatorBuilder<AppData>()
|
||||
.setOrderForModel(AppData::class.java) { a, b -> a.title?.compareTo(b.title!!)!! }
|
||||
.build()
|
||||
|
||||
class AppAdapter(val context: Context) : SortedListAdapter<AppData>(context, AppData::class.java, comparator) {
|
||||
private val LIST_ITEM = 0
|
||||
private val GRID_ITEM = 1
|
||||
private var isSwitchView = true
|
||||
|
||||
inner class AppViewHolder(itemView: View) :
|
||||
SortedListAdapter.ViewHolder<AppData?>(itemView) {
|
||||
var appTitle: TextView = itemView.findViewById(R.id.appTitle)
|
||||
var appRegion: TextView = itemView.findViewById(R.id.appRegion)
|
||||
var appMinFW: TextView = itemView.findViewById(R.id.appMinFW)
|
||||
var appLastDate: TextView = itemView.findViewById(R.id.appLastDate)
|
||||
|
||||
override fun performBind(item: AppData) {
|
||||
appTitle.text = item.titleID + " | " + item.title
|
||||
appTitle.isSelected = true
|
||||
appRegion.text = item.region
|
||||
appMinFW.text = item.minFW
|
||||
appMinFW.background = null;
|
||||
|
||||
if (appMinFW.text.isNotBlank()) {
|
||||
val drawable = ResourcesCompat.getDrawable(
|
||||
context.resources,
|
||||
R.drawable.rect_red,
|
||||
null
|
||||
)
|
||||
appMinFW.background = drawable;
|
||||
}
|
||||
appLastDate.text = item.lastDateTime
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup, viewType: Int): AppViewHolder {
|
||||
return if (viewType == LIST_ITEM) {
|
||||
AppViewHolder(
|
||||
inflater.inflate(
|
||||
R.layout.recycler_item_list,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
} else {
|
||||
AppViewHolder(
|
||||
inflater.inflate(
|
||||
R.layout.recycler_item_grid,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (isSwitchView) {
|
||||
LIST_ITEM
|
||||
} else {
|
||||
GRID_ITEM
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleItemViewType(type: Int): Boolean {
|
||||
isSwitchView = type == 0
|
||||
return isSwitchView
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.illusionware.npsbrowser.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.illusionware.npsbrowser.R
|
||||
|
||||
class SettingsFragment : Fragment() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
PreferenceManager.setDefaultValues(requireContext(), R.xml.root_preferences, false)
|
||||
activity?.supportFragmentManager
|
||||
?.beginTransaction()
|
||||
?.replace(
|
||||
R.id.prefs,
|
||||
PrefsFragment()
|
||||
)
|
||||
?.setTransition(TRANSIT_FRAGMENT_OPEN)
|
||||
?.commit()
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
activity?.title = resources.getString(R.string.title_activity_settings)
|
||||
return inflater.inflate(R.layout.settings_fragment, container, false)
|
||||
}
|
||||
|
||||
class PrefsFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||
PreferenceManager.setDefaultValues(requireContext(), R.xml.root_preferences, false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
package com.illusionware.npsbrowser.fragments
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import android.content.Context.DOWNLOAD_SERVICE
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import com.bumptech.glide.Glide
|
||||
import com.illusionware.npsbrowser.AppData
|
||||
import com.illusionware.npsbrowser.R
|
||||
|
||||
class SingleAppFragment : Fragment() {
|
||||
private val baseStoreURL = "https://store.playstation.com/store/api/chihiro/00_09_000/container"
|
||||
|
||||
companion object {
|
||||
lateinit var app : AppData
|
||||
}
|
||||
|
||||
// TODO: either use binding or make vars for all the views
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
activity?.title = app.title
|
||||
val view = inflater.inflate(R.layout.single_app_fragment, container, false)
|
||||
val downloadManager = requireActivity().getSystemService(DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
||||
view?.findViewById<TextView>(R.id.singleAppTitle)?.text = "[${app.titleID}] ${app.title}"
|
||||
if (app.fileSize != null) {
|
||||
view?.findViewById<TextView>(R.id.singleAppSize)?.text = prettifySize()
|
||||
} else {
|
||||
view?.findViewById<TextView>(R.id.singleAppSizeTitle)?.visibility = View.INVISIBLE
|
||||
}
|
||||
view?.findViewById<TextView>(R.id.singleAppContentId)?.text = app.contentID
|
||||
view?.findViewById<TextView>(R.id.singleAppFwVer)?.text = app.minFW
|
||||
// TODO: get and set app icon
|
||||
if (app.minFW.isNullOrBlank()) {
|
||||
view?.findViewById<TextView>(R.id.singleAppFwVerTitle)?.visibility = View.INVISIBLE
|
||||
}
|
||||
if (app.license == "MISSING") {
|
||||
view?.findViewById<TextView>(R.id.singleAppError)?.text = getString(R.string.no_license)
|
||||
view?.findViewById<Button>(R.id.singleAppCopyZRIFButton)?.isEnabled = false
|
||||
}
|
||||
if (app.link == "CART ONLY") {
|
||||
view?.findViewById<TextView>(R.id.singleAppError)?.text = getString(R.string.cart_only)
|
||||
view?.findViewById<Button>(R.id.singleAppDownloadButton)?.isEnabled = false
|
||||
view?.findViewById<Button>(R.id.singleAppCopyZRIFButton)?.isEnabled = false
|
||||
} else if (app.link == "MISSING") {
|
||||
view?.findViewById<TextView>(R.id.singleAppError)?.text = getString(R.string.no_download_link)
|
||||
view?.findViewById<Button>(R.id.singleAppDownloadButton)?.isEnabled = false
|
||||
view?.findViewById<Button>(R.id.singleAppCopyZRIFButton)?.isEnabled = false
|
||||
}
|
||||
|
||||
view?.findViewById<TextView>(R.id.singleAppContentId)?.setOnLongClickListener {
|
||||
val clipboardManager = requireActivity().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clipData = ClipData.newPlainText(
|
||||
"Content ID",
|
||||
view.findViewById<TextView>(R.id.singleAppContentId)?.text
|
||||
)
|
||||
clipboardManager.setPrimaryClip(clipData)
|
||||
Toast.makeText(requireContext(), getString(R.string.content_id_copied), Toast.LENGTH_SHORT).show()
|
||||
true
|
||||
}
|
||||
|
||||
// click listener for the download button
|
||||
view?.findViewById<Button>(R.id.singleAppDownloadButton)?.setOnClickListener {
|
||||
try {
|
||||
val request = DownloadManager.Request(Uri.parse(app.link))
|
||||
request.setTitle(app.title)
|
||||
request.setDescription(getString(R.string.downloading))
|
||||
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
request.setDestinationInExternalPublicDir(
|
||||
Environment.DIRECTORY_DOWNLOADS,
|
||||
app.title!!.replace("[\\\\/:*?\"<>|]".toRegex(), " ") + ".pkg"
|
||||
) // TODO: set up preference for this
|
||||
downloadManager.enqueue(request)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
getString(R.string.download_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
// long click listener for download button
|
||||
view?.findViewById<Button>(R.id.singleAppDownloadButton)?.setOnLongClickListener {
|
||||
val clipboardManager = requireActivity().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clipData = ClipData.newPlainText("PKG URL", app.link)
|
||||
clipboardManager.setPrimaryClip(clipData)
|
||||
Toast.makeText(requireContext(), getString(R.string.pkg_url_copied), Toast.LENGTH_SHORT).show()
|
||||
true
|
||||
}
|
||||
|
||||
// click listener for the copyzrifbutton button
|
||||
view?.findViewById<Button>(R.id.singleAppCopyZRIFButton)?.setOnClickListener {
|
||||
val clipboardManager = requireActivity().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clipData = ClipData.newPlainText("zRIF", app.license)
|
||||
clipboardManager.setPrimaryClip(clipData)
|
||||
Toast.makeText(requireContext(),getString(R.string.zrif_copied),Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
// set the height for the error text to 0 if there's no error so that the scrollview can take more space
|
||||
if (view?.findViewById<TextView>(R.id.singleAppError)?.text.isNullOrEmpty()) {
|
||||
view?.findViewById<TextView>(R.id.singleAppError)?.layoutParams?.height = 0
|
||||
}
|
||||
|
||||
if (app.contentID != null) {
|
||||
val circularProgressDrawable = CircularProgressDrawable(requireContext())
|
||||
circularProgressDrawable.setColorSchemeColors(0x43EB92)
|
||||
circularProgressDrawable.strokeWidth = 10f
|
||||
circularProgressDrawable.centerRadius = 180f
|
||||
circularProgressDrawable.start()
|
||||
|
||||
val image : ImageView? = view?.findViewById(R.id.singleAppIcon)
|
||||
val iconURL = getImage(app.contentID!!)
|
||||
if (iconURL != null) {
|
||||
// TODO: make the options a requestOptions
|
||||
Glide.with(this).load(iconURL).placeholder(circularProgressDrawable).error(R.drawable.ic_games_placeholder_24dp).into(image!!)
|
||||
}
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun prettifySize(): String {
|
||||
if (app.fileSize!!.toFloat() / (1024 * 1024 * 1024) > 1) {
|
||||
return "%.1f GB".format(toGB(app.fileSize!!))
|
||||
}
|
||||
if (app.fileSize!!.toFloat() / (1024 * 1024) > 1) {
|
||||
return "%.1f MB".format(toMB(app.fileSize!!))
|
||||
}
|
||||
if (app.fileSize!!.toFloat() / 1024 > 1) {
|
||||
return "%.1f KB".format(toKB(app.fileSize!!))
|
||||
}
|
||||
return "${app.fileSize!!} Bytes"
|
||||
}
|
||||
|
||||
private fun toKB(size: Number): Number {
|
||||
return size.toFloat() / 1024
|
||||
}
|
||||
|
||||
private fun toMB(size: Number): Number {
|
||||
return size.toFloat() / (1024 * 1024)
|
||||
}
|
||||
|
||||
private fun toGB(size: Number): Number {
|
||||
return size.toFloat() / (1024 * 1024 * 1024)
|
||||
}
|
||||
|
||||
private fun getImage(contentID: String): String? {
|
||||
when (contentID.substring(0, 2)) {
|
||||
"EP" -> {
|
||||
// european games
|
||||
return "$baseStoreURL/SA/en/999/${contentID}/image"
|
||||
}
|
||||
"UP" -> {
|
||||
// united states games
|
||||
return "$baseStoreURL/US/en/999/${contentID}/image"
|
||||
}
|
||||
"JP" -> {
|
||||
// japanese games
|
||||
return "$baseStoreURL/jp/ja/999/${contentID}/image"
|
||||
}
|
||||
"KP" -> {
|
||||
// korean games
|
||||
return "$baseStoreURL/kr/ko/999/${contentID}/image"
|
||||
}
|
||||
"HP" -> {
|
||||
// asia games
|
||||
return "$baseStoreURL/HK/zh/999/${contentID}/image"
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
-283
@@ -1,283 +0,0 @@
|
||||
package com.illusionware.npsbrowser.fragments.mainactivity
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.content.ContextCompat.checkSelfPermission
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.illusionware.npsbrowser.AppData
|
||||
import com.illusionware.npsbrowser.R
|
||||
import com.illusionware.npsbrowser.RecyclerTouchListener
|
||||
import com.illusionware.npsbrowser.fragments.SingleAppFragment
|
||||
import com.illusionware.npsbrowser.adapters.AppAdapter
|
||||
import com.illusionware.npsbrowser.fragments.SettingsFragment
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class MainActivityFragment : Fragment() {
|
||||
|
||||
var recyclerView : RecyclerView? = null
|
||||
var viewAdapter: AppAdapter? = null
|
||||
var filteredModelList: ArrayList<AppData>? = null
|
||||
|
||||
companion object {
|
||||
var apps : ArrayList<AppData>? = arrayListOf()
|
||||
}
|
||||
|
||||
private val tsvReader = csvReader {
|
||||
charset = "UTF-8"
|
||||
quoteChar = '\\'
|
||||
escapeChar = '\\'
|
||||
delimiter = '\t'
|
||||
skipEmptyLine = true
|
||||
skipMissMatchedRow = true
|
||||
}
|
||||
|
||||
private lateinit var viewModel: MainActivityViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
setHasOptionsMenu(true)
|
||||
activity?.title = resources.getString(R.string.app_name)
|
||||
|
||||
val view = inflater.inflate(R.layout.main_fragment, container, false)
|
||||
|
||||
view?.findViewById<FloatingActionButton>(R.id.tsvChooserButton)?.setOnClickListener {
|
||||
if (!isStoragePermissionGranted())
|
||||
return@setOnClickListener
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "text/tab-separated-values"
|
||||
}
|
||||
startActivityForResult(intent, 1)
|
||||
}
|
||||
viewModel = ViewModelProviders.of(this).get(MainActivityViewModel::class.java)
|
||||
// TODO: Use the ViewModel
|
||||
|
||||
recyclerView = view?.findViewById(R.id.appsRecycler)
|
||||
|
||||
if (viewAdapter == null) {
|
||||
viewAdapter = AppAdapter(requireContext())
|
||||
viewAdapter?.edit()?.add(apps!!)?.commit()
|
||||
}
|
||||
|
||||
if (apps != null) {
|
||||
filteredModelList = apps
|
||||
}
|
||||
viewAdapter?.notifyDataSetChanged()
|
||||
|
||||
recyclerView?.adapter = viewAdapter
|
||||
|
||||
val orientation = resources.configuration.orientation
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val isViewAsList = prefs.getString("layout_type", "0")
|
||||
if (isViewAsList == "0") {
|
||||
viewAdapter?.toggleItemViewType(0)
|
||||
recyclerView?.layoutManager = LinearLayoutManager(context)
|
||||
viewAdapter?.notifyDataSetChanged()
|
||||
} else {
|
||||
viewAdapter?.toggleItemViewType(1)
|
||||
recyclerView?.layoutManager = GridLayoutManager(
|
||||
context,
|
||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) 4 else 2
|
||||
)
|
||||
viewAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
recyclerView?.addOnItemTouchListener(RecyclerTouchListener(
|
||||
requireContext(),
|
||||
recyclerView!!,
|
||||
object : RecyclerTouchListener.ClickListener {
|
||||
override fun onClick(view: View?, position: Int) {
|
||||
SingleAppFragment.app = filteredModelList!![position]
|
||||
activity?.supportFragmentManager
|
||||
?.beginTransaction()
|
||||
?.replace(R.id.container,
|
||||
SingleAppFragment()
|
||||
)
|
||||
?.addToBackStack("single app")
|
||||
?.commit()
|
||||
}
|
||||
|
||||
override fun onLongClick(
|
||||
view: View?,
|
||||
recyclerView: RecyclerView?,
|
||||
position: Int
|
||||
) {
|
||||
Toast.makeText(context, "TODO: Long Click $position", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
))
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, resultData)
|
||||
if (requestCode == 1 && resultCode == Activity.RESULT_OK) {
|
||||
resultData?.data?.also { uri ->
|
||||
val inputStream: InputStream? = activity?.contentResolver?.openInputStream(uri)
|
||||
val entries : List<Map<String, String?>> = tsvReader.readAllWithHeader(inputStream!!)
|
||||
apps?.clear()
|
||||
apps = ArrayList(entries.size)
|
||||
|
||||
entries.forEach { entry ->
|
||||
if (entry["Name"].isNullOrEmpty()) {
|
||||
return@forEach
|
||||
}
|
||||
val id = entry["Title ID"]
|
||||
val region = entry["Region"]
|
||||
val name = entry["Name"]
|
||||
val link = entry["PKG direct link"]
|
||||
val license = entry["zRIF"]
|
||||
val contentID = entry["Content ID"]
|
||||
var lastDateTime = entry["Last Modification Date"]
|
||||
if (!lastDateTime.isNullOrEmpty()) {
|
||||
lastDateTime = lastDateTime.substring(0, 10)
|
||||
}
|
||||
val fileSize = entry["File Size"]?.toLongOrNull()
|
||||
val sha256 = entry["SHA256"]
|
||||
val minFW = entry["Required FW"]
|
||||
val app = AppData(
|
||||
id, region, name, link ?: "MISSING", license ?: "MISSING",
|
||||
contentID ?: "MISSING", lastDateTime ?: "MISSING",
|
||||
fileSize, sha256 ?: "MISSING", minFW
|
||||
)
|
||||
apps?.add(app)
|
||||
}
|
||||
apps?.sortBy { it.title }
|
||||
filteredModelList = apps
|
||||
|
||||
recyclerView = view?.findViewById(R.id.appsRecycler)
|
||||
viewAdapter = AppAdapter(requireContext())
|
||||
viewAdapter?.edit()?.add(apps!!)?.commit()
|
||||
val orientation = this.resources.configuration.orientation
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val isViewAsList = prefs.getString("layout_type", "0")
|
||||
|
||||
if (isViewAsList == "0") {
|
||||
viewAdapter?.toggleItemViewType(0)
|
||||
recyclerView?.layoutManager = LinearLayoutManager(context)
|
||||
viewAdapter?.notifyDataSetChanged()
|
||||
} else {
|
||||
viewAdapter?.toggleItemViewType(1)
|
||||
recyclerView?.layoutManager = GridLayoutManager(
|
||||
context,
|
||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) 4 else 2
|
||||
)
|
||||
viewAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
recyclerView?.adapter = viewAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.main, menu)
|
||||
|
||||
val searchItem = menu.findItem(R.id.action_search)
|
||||
val searchView: SearchView = searchItem.actionView as SearchView
|
||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(query: String): Boolean {
|
||||
filteredModelList = filter(apps!!, query) as ArrayList<AppData>?
|
||||
viewAdapter?.edit()?.replaceAll(filteredModelList!!)?.commit()
|
||||
recyclerView?.scrollToPosition(0)
|
||||
return false
|
||||
}
|
||||
})
|
||||
super.onCreateOptionsMenu(menu, menuInflater)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_settings -> {
|
||||
activity?.supportFragmentManager
|
||||
?.beginTransaction()
|
||||
?.replace(
|
||||
R.id.container,
|
||||
SettingsFragment()
|
||||
)
|
||||
?.addToBackStack("settings")
|
||||
?.commit()
|
||||
true
|
||||
}
|
||||
R.id.action_search -> {
|
||||
true
|
||||
}
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
viewAdapter?.edit()?.replaceAll(apps!!)?.commit()
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
val orientation = newConfig.orientation
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val isViewAsList = prefs.getString("layout_type", "0")
|
||||
if (isViewAsList == "0") {
|
||||
val pos = (recyclerView?.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
||||
viewAdapter?.toggleItemViewType(0)
|
||||
recyclerView?.layoutManager = LinearLayoutManager(context)
|
||||
(recyclerView?.layoutManager as LinearLayoutManager).scrollToPosition(pos)
|
||||
viewAdapter?.notifyDataSetChanged()
|
||||
} else {
|
||||
val pos = (recyclerView?.layoutManager as GridLayoutManager).findFirstVisibleItemPosition()
|
||||
viewAdapter?.toggleItemViewType(1)
|
||||
recyclerView?.layoutManager = GridLayoutManager(
|
||||
context,
|
||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) 4 else 2
|
||||
)
|
||||
(recyclerView?.layoutManager as GridLayoutManager).scrollToPosition(pos)
|
||||
viewAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isStoragePermissionGranted(): Boolean {
|
||||
return if (checkSelfPermission(
|
||||
this.requireContext(),
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_GRANTED) {
|
||||
true
|
||||
} else {
|
||||
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 1)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun filter(models: List<AppData>, query: String): List<AppData>? {
|
||||
val lowerCaseQuery = query.lowercase(Locale.getDefault())
|
||||
val filteredModelList: MutableList<AppData> = ArrayList()
|
||||
for (model in models) {
|
||||
val title: String = model.title?.lowercase(Locale.getDefault())!!
|
||||
val id: String = model.titleID?.lowercase(Locale.getDefault())!!
|
||||
if (title.contains(lowerCaseQuery) || id.contains(lowerCaseQuery)) {
|
||||
filteredModelList.add(model)
|
||||
}
|
||||
}
|
||||
return filteredModelList
|
||||
}
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
package com.illusionware.npsbrowser.fragments.mainactivity
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class MainActivityViewModel : ViewModel() {
|
||||
// TODO: Implement the ViewModel
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.illusionware.npsbrowser.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.AlertDialogDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.illusionware.npsbrowser.ui.theme.Typography
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NPSAlertDialog(
|
||||
onDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
buttons: @Composable () -> Unit,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
Surface (
|
||||
modifier = Modifier.wrapContentHeight().wrapContentWidth(),
|
||||
shape = MaterialTheme.shapes.large,
|
||||
tonalElevation = AlertDialogDefaults.TonalElevation
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.padding(24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Text(text = title, style = Typography.titleLarge)
|
||||
content()
|
||||
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
|
||||
buttons()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.illusionware.npsbrowser.ui.components
|
||||
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.PlainTooltipBox
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NPSIconButton(tooltip: String, onClick: () -> Unit, content: @Composable () -> Unit) {
|
||||
PlainTooltipBox(tooltip = {
|
||||
Text(text = tooltip)
|
||||
}) {
|
||||
IconButton(onClick = onClick, Modifier.tooltipAnchor()) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.illusionware.npsbrowser.ui.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun NPSRadioButton(text: String = "Radio Button", onClick: () -> Unit = {}, selected: Boolean = true) {
|
||||
Box (Modifier.clickable { onClick() }.fillMaxWidth()) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
RadioButton(selected = selected, onClick = onClick)
|
||||
Text(text = text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
package com.illusionware.npsbrowser.ui.pages
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.PickVisualMediaRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProvideTextStyle
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.illusionware.npsbrowser.R
|
||||
import com.illusionware.npsbrowser.ui.theme.ColorAccent
|
||||
import com.illusionware.npsbrowser.ui.theme.Typography
|
||||
import com.illusionware.npsbrowser.ui.theme.md_theme_dark_onSurface
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.ButtonElevation
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.layout.layout
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.role
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
const val PAGE_COUNT = 3;
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
@Composable
|
||||
fun OnBoarding(navigateToHome: () -> Unit = {}, darkTheme: Boolean = false) {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val pagerState = rememberPagerState()
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
|
||||
val darkIcons = when (pagerState.currentPage) {
|
||||
0 -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
DisposableEffect(systemUiController, darkIcons) {
|
||||
systemUiController.setStatusBarColor(
|
||||
color = Color.Transparent,
|
||||
darkIcons = darkIcons && !darkTheme
|
||||
)
|
||||
val activity = context as Activity
|
||||
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
onDispose {
|
||||
systemUiController.setStatusBarColor(
|
||||
color = Color.Transparent,
|
||||
darkIcons = !darkTheme
|
||||
)
|
||||
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalPager(pageCount = PAGE_COUNT, state = pagerState) { page ->
|
||||
when (page) {
|
||||
0 -> OnBoardingInitial(incrementPage = { scope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) } })
|
||||
1 -> OnBoardingSetupTSVs(
|
||||
page = page,
|
||||
incrementPage = { scope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) } },
|
||||
skipToEnd = { scope.launch { pagerState.animateScrollToPage(PAGE_COUNT - 1) } }
|
||||
)
|
||||
2 -> OnBoardingSetupSettings(
|
||||
page = page,
|
||||
incrementPage = { scope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) } },
|
||||
decrementPage = { scope.launch { pagerState.animateScrollToPage(pagerState.currentPage - 1) } },
|
||||
skipToEnd = { scope.launch { pagerState.animateScrollToPage(PAGE_COUNT - 1) } },
|
||||
navigateToHome = navigateToHome
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun OnBoardingInitial(incrementPage: () -> Unit = {}) {
|
||||
val density = LocalDensity.current
|
||||
var visible by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(key1 = visible) {
|
||||
visible = true
|
||||
}
|
||||
|
||||
Scaffold(containerColor = ColorAccent, contentColor = MaterialTheme.colorScheme.onBackground) { padding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.statusBarsPadding(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(56.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = slideInVertically {
|
||||
h -> h
|
||||
} + fadeIn(
|
||||
initialAlpha = 0.2f
|
||||
),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.nps_fg_onboarding),
|
||||
contentDescription = "App Logo",
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 64.dp)
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = slideInVertically(tween(delayMillis = 100)) {
|
||||
with(density) { 50.dp.roundToPx() }
|
||||
} + fadeIn(
|
||||
animationSpec = tween(delayMillis = 50),
|
||||
initialAlpha = 0.0f,
|
||||
),
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(48.dp)
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
ProvideTextStyle(value = Typography.displayMedium.copy(color = Color.White)) {
|
||||
Text(text = "No Ads.")
|
||||
Text(text = "No Waiting.")
|
||||
Text(text = "No Bullshit.")
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = "Browse and download from a huge library of games, themes, and DLC to your heart's content.",
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 32.dp),
|
||||
style = Typography.titleLarge.copy(color = md_theme_dark_onSurface),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(
|
||||
animationSpec = tween(delayMillis = 200, durationMillis = 500),
|
||||
initialAlpha = 0.0f,
|
||||
),
|
||||
) {
|
||||
TextButton(
|
||||
onClick = incrementPage,
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = Color.White, containerColor = Color(0xFF1A91EC)),
|
||||
contentPadding = PaddingValues(horizontal = 24.dp)
|
||||
) {
|
||||
Text(text = "Get Started")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun OnBoardingSetupTSVs(
|
||||
page: Int = 1,
|
||||
incrementPage: () -> Unit = {},
|
||||
skipToEnd: () -> Unit = {}
|
||||
) {
|
||||
Scaffold() { padding ->
|
||||
Column(
|
||||
Modifier
|
||||
.padding(padding)
|
||||
.padding(horizontal = 16.dp, vertical = 32.dp)
|
||||
.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(text = "Setup TSVs", style = Typography.displayMedium)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "You can set up your TSV files on this screen",
|
||||
style = Typography.titleMedium.copy(color = MaterialTheme.colorScheme.outline),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(
|
||||
text = "You are expected to provide the TSV files yourself. NPS Browser will NOT provide you with these files",
|
||||
style = Typography.titleSmall.copy(color = Color(0xFFD63A3A)),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.weight(1.0f, fill = false)
|
||||
.padding(vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
TSVGroup(title = "PS Vita") {
|
||||
TSVItem("Games")
|
||||
TSVItem("Themes")
|
||||
TSVItem("DLC")
|
||||
}
|
||||
TSVGroup(title = "PSP") {
|
||||
TSVItem("Games")
|
||||
TSVItem("DLC")
|
||||
Box(modifier = Modifier.width(110.dp))
|
||||
}
|
||||
TSVGroup(title = "PS3") {
|
||||
TSVItem("Games")
|
||||
TSVItem("DLC")
|
||||
Box(modifier = Modifier.width(110.dp))
|
||||
}
|
||||
TSVGroup(title = "PSX") {
|
||||
TSVItem("Games")
|
||||
}
|
||||
TSVGroup(title = "PSM") {
|
||||
TSVItem("Games")
|
||||
}
|
||||
}
|
||||
OnBoardingBottomButtons(
|
||||
page = page,
|
||||
incrementPage = incrementPage,
|
||||
skipToEnd = skipToEnd,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TSVGroup(title: String, content: @Composable RowScope.() -> Unit) {
|
||||
Column() {
|
||||
Text(
|
||||
text = title,
|
||||
style = TextStyle.Default.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp,
|
||||
),
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TSVItem(title: String) {
|
||||
var hasSelected by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val filePicker = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.GetContent(),
|
||||
) { fileUri ->
|
||||
if (fileUri != null) {
|
||||
Log.d("TSVItem", "fileUri: $fileUri")
|
||||
hasSelected = true
|
||||
}
|
||||
}
|
||||
FilledTonalButton(
|
||||
onClick = {
|
||||
filePicker.launch("text/tab-separated-values")
|
||||
},
|
||||
modifier = Modifier
|
||||
.width(110.dp)
|
||||
.height(160.dp)
|
||||
.padding(vertical = 8.dp, horizontal = 0.dp)
|
||||
.graphicsLayer(clip = false),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
colors = ButtonDefaults.filledTonalButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||
containerColor = if (!hasSelected) MaterialTheme.colorScheme.secondaryContainer else Color(0x335EC03C)
|
||||
),
|
||||
) {
|
||||
Layout(
|
||||
modifier = Modifier.graphicsLayer(clip = false),
|
||||
content = {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.tsv_file),
|
||||
contentDescription = "Add TSV",
|
||||
modifier = Modifier
|
||||
.weight(1.0f)
|
||||
.fillMaxWidth(),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
Text(text = title, style = Typography.titleMedium)
|
||||
}
|
||||
Icon(
|
||||
modifier = Modifier.layoutId("selected"),
|
||||
imageVector = Icons.Filled.CheckCircle,
|
||||
contentDescription = "Selected",
|
||||
tint = Color(0xFF5EC03C)
|
||||
)
|
||||
}
|
||||
) { measurables, constraints ->
|
||||
val buttonPlaceable = measurables[0].measure(constraints)
|
||||
|
||||
val iconPlaceable = measurables.first { it.layoutId == "selected" }.measure(constraints)
|
||||
|
||||
layout(buttonPlaceable.width, buttonPlaceable.height) {
|
||||
if (hasSelected) {
|
||||
iconPlaceable.placeRelative(
|
||||
x = buttonPlaceable.width - iconPlaceable.width,
|
||||
y = -8,
|
||||
)
|
||||
}
|
||||
buttonPlaceable.placeRelative(0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OnBoardingBottomButtons(
|
||||
page: Int,
|
||||
incrementPage: () -> Unit = {},
|
||||
decrementPage: () -> Unit = {},
|
||||
skipToEnd: () -> Unit = {},
|
||||
navigateToHome: () -> Unit = {},
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(0.1f),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
FilledTonalButton(
|
||||
onClick = skipToEnd,
|
||||
colors = ButtonDefaults.filledTonalButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.primary
|
||||
),
|
||||
contentPadding = PaddingValues(horizontal = 18.dp)
|
||||
) {
|
||||
Text(text = "Skip")
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
if (page != 1) {
|
||||
Button(onClick = decrementPage, contentPadding = PaddingValues(horizontal = 18.dp)) {
|
||||
Text(text = "Prev")
|
||||
}
|
||||
}
|
||||
if (page == 2) {
|
||||
Button(onClick = navigateToHome, contentPadding = PaddingValues(horizontal = 18.dp)) {
|
||||
Text(text = "Finish")
|
||||
}
|
||||
} else {
|
||||
Button(
|
||||
onClick = incrementPage,
|
||||
contentPadding = PaddingValues(horizontal = 18.dp)
|
||||
) {
|
||||
Text(text = "Next")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun OnBoardingSetupSettings(
|
||||
page: Int = 2,
|
||||
incrementPage: () -> Unit = {},
|
||||
decrementPage: () -> Unit = {},
|
||||
skipToEnd: () -> Unit = {},
|
||||
navigateToHome: () -> Unit = {},
|
||||
) {
|
||||
Scaffold() { padding ->
|
||||
Column(Modifier.padding(padding)) {
|
||||
Text(text = "Configure App")
|
||||
OnBoardingBottomButtons(
|
||||
page = page,
|
||||
incrementPage = incrementPage,
|
||||
decrementPage = decrementPage,
|
||||
skipToEnd = skipToEnd,
|
||||
navigateToHome = navigateToHome
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package com.illusionware.npsbrowser.ui.pages
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.FileDownload
|
||||
import androidx.compose.material.icons.outlined.Key
|
||||
import androidx.compose.material.icons.outlined.Palette
|
||||
import androidx.compose.material.icons.outlined.Unarchive
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.illusionware.npsbrowser.Preferences
|
||||
import com.illusionware.npsbrowser.R
|
||||
import com.illusionware.npsbrowser.ui.components.NPSAlertDialog
|
||||
import com.illusionware.npsbrowser.ui.components.NPSIconButton
|
||||
import com.illusionware.npsbrowser.ui.components.NPSRadioButton
|
||||
import com.illusionware.npsbrowser.ui.theme.ColorSecondaryLight
|
||||
import com.illusionware.npsbrowser.ui.theme.Typography
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun SettingsScreen(navigationGoBack: () -> Unit = {}, onThemeChange: (v: Int) -> Unit = {}) {
|
||||
Scaffold (
|
||||
topBar = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.padding(vertical = 8.dp, horizontal = 4.dp),
|
||||
) {
|
||||
NPSIconButton(tooltip = "Go Back", onClick = { navigationGoBack() }) {
|
||||
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "Go Back" )
|
||||
}
|
||||
Text(text = stringResource(id = R.string.title_activity_settings), style = Typography.titleLarge)
|
||||
}
|
||||
},
|
||||
) { padding ->
|
||||
Column(modifier = Modifier
|
||||
.padding(padding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(20.dp), modifier = Modifier.padding(vertical = 16.dp)) {
|
||||
Appearance(onThemeChange)
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Header("Updates")
|
||||
Spacer(modifier = Modifier.size(2.dp))
|
||||
DialogSetting(title = "HMAC Key", value = "None", icon = Icons.Outlined.Key)
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Header("Downloads")
|
||||
Spacer(modifier = Modifier.size(2.dp))
|
||||
ToggleSetting(title = "Unpack in Download Directory")
|
||||
DialogSetting(title = "Download Directory", value = "sdcard/Downloads", icon = Icons.Outlined.FileDownload)
|
||||
DialogSetting(title = "Unpack Directory", value = "sdcard/unpack", icon = Icons.Outlined.Unarchive)
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Header("Pkg2zip")
|
||||
Spacer(modifier = Modifier.size(2.dp))
|
||||
ToggleSetting(title = "Automatically decrypt downloaded content")
|
||||
DialogSetting(title = "Decryption params", value = "-x {pkgFile} \"{zRifKey}\"", icon = painterResource(
|
||||
id = R.drawable.lock_open
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingGroup(title: String, content: @Composable () -> Unit) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Header(title)
|
||||
Spacer(modifier = Modifier.size(2.dp))
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
val Themes = hashMapOf(
|
||||
0 to "Light",
|
||||
1 to "Dark",
|
||||
2 to "Use System Theme"
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun Appearance(onThemeChange: (v: Int) -> Unit) {
|
||||
val prefs = Preferences(LocalContext.current)
|
||||
val theme = prefs.theme.collectAsState(initial = 0)
|
||||
|
||||
var themeOpenDialog by remember { mutableStateOf(false) }
|
||||
|
||||
SettingGroup(title = stringResource(id = R.string.appearance)) {
|
||||
DialogSetting(
|
||||
title = stringResource(id = R.string.theme),
|
||||
value = Themes[theme.value] ?: "None",
|
||||
icon = Icons.Outlined.Palette,
|
||||
onClick = { themeOpenDialog = true }
|
||||
)
|
||||
}
|
||||
if (themeOpenDialog) {
|
||||
ThemeDialog(
|
||||
onDismiss = { themeOpenDialog = false },
|
||||
onThemeChange = onThemeChange
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ThemeDialog(onDismiss: () -> Unit, onThemeChange: (v: Int) -> Unit) {
|
||||
val prefs = Preferences(LocalContext.current)
|
||||
val theme = prefs.theme.collectAsState(initial = "")
|
||||
|
||||
NPSAlertDialog(
|
||||
onDismiss = onDismiss,
|
||||
title = stringResource(id = R.string.theme),
|
||||
buttons = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(text = "Cancel")
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column {
|
||||
NPSRadioButton(
|
||||
text = "Light",
|
||||
onClick = { CoroutineScope(Dispatchers.IO).launch {
|
||||
onThemeChange(0)
|
||||
prefs.changeTheme(0) }
|
||||
},
|
||||
selected = theme.value == 0,
|
||||
)
|
||||
NPSRadioButton(
|
||||
text = "Dark",
|
||||
onClick = { CoroutineScope(Dispatchers.IO).launch {
|
||||
onThemeChange(1)
|
||||
prefs.changeTheme(1) }
|
||||
},
|
||||
selected = theme.value == 1,
|
||||
)
|
||||
NPSRadioButton(
|
||||
text = "Use System Theme",
|
||||
onClick = { CoroutineScope(Dispatchers.IO).launch {
|
||||
onThemeChange(2)
|
||||
prefs.changeTheme(2) }
|
||||
},
|
||||
selected = theme.value == 2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Header(title: String) {
|
||||
Row(Modifier.padding(start = 56.dp)) {
|
||||
Text(text = title, color = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
// TODO: remove the default value for onClick
|
||||
fun DialogSetting(title: String, value: String, icon: Painter, onClick: () -> Unit = {}) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
) {
|
||||
Icon(painter = icon, contentDescription = "TODO:", modifier = Modifier.width(56.dp))
|
||||
Column {
|
||||
Text(text = title, color = MaterialTheme.colorScheme.onBackground)
|
||||
Text(text = value, color = ColorSecondaryLight)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
// TODO: remove the default value for onClick
|
||||
fun DialogSetting(title: String, value: String, icon: ImageVector, onClick: () -> Unit = {}) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
) {
|
||||
Icon(imageVector = icon, contentDescription = "TODO:", modifier = Modifier.width(56.dp))
|
||||
Column {
|
||||
Text(text = title, color = MaterialTheme.colorScheme.onBackground)
|
||||
Text(text = value, color = ColorSecondaryLight)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToggleSetting(title: String, icon: ImageVector? = null) {
|
||||
Box(
|
||||
modifier = Modifier.clickable { /* TODO: */ },
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.padding(top = 8.dp, end = 24.dp, bottom = 8.dp),
|
||||
) {
|
||||
if (icon != null) {
|
||||
Icon(imageVector = icon, contentDescription = "TODO:", Modifier.width(56.dp))
|
||||
} else {
|
||||
Box(modifier = Modifier.width(56.dp))
|
||||
}
|
||||
Text(text = title, color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.weight(1.0f))
|
||||
Switch(checked = true, onCheckedChange = { /* TODO: */ })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.illusionware.npsbrowser.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val ColorPrimaryLight = Color(0xFFFFFFFF)
|
||||
val ColorPrimaryVariantLight = Color(0xFFEFEFEF)
|
||||
val ColorOnPrimaryLight = Color(0xFF000000)
|
||||
val ColorSecondaryLight = Color(0xFF999999)
|
||||
val ColorSecondaryVariantLight = Color(0xFF888888)
|
||||
val ColorOnSecondaryLight = Color(0xFF111111)
|
||||
val ColorAmoledLight = Color(0xFFFFFFFF)
|
||||
|
||||
val ColorPrimaryDark = Color(0xFF222222)
|
||||
val ColorPrimaryVariantDark = Color(0xFF202020)
|
||||
val ColorOnPrimaryDark = Color(0xFFFFFFFF)
|
||||
val ColorSecondaryDark = Color(0xFF696969)
|
||||
val ColorSecondaryVariantDark = Color(0xFF515151)
|
||||
val ColorOnSecondaryDark = Color(0xDFFFFFFF)
|
||||
val ColorAmoledDark = Color(0xFF000000)
|
||||
|
||||
// Works on both Dark and Light
|
||||
val ColorAccent = Color(0xFF545FBF)
|
||||
val ColorError = Color(0xFFF93422)
|
||||
|
||||
// Light Mode
|
||||
val md_theme_light_primary = Color(0xFF5951B3)
|
||||
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_primaryContainer = Color(0xFFE4DFFF)
|
||||
val md_theme_light_onPrimaryContainer = Color(0xFF140067)
|
||||
val md_theme_light_secondary = Color(0xFF5E5C71)
|
||||
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_secondaryContainer = Color(0xFFE4DFF9)
|
||||
val md_theme_light_onSecondaryContainer = Color(0xFF1B192C)
|
||||
val md_theme_light_tertiary = Color(0xFF5754A8)
|
||||
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_tertiaryContainer = Color(0xFFE3DFFF)
|
||||
val md_theme_light_onTertiaryContainer = Color(0xFF110563)
|
||||
val md_theme_light_error = Color(0xFFBA1A1A)
|
||||
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
|
||||
val md_theme_light_onError = Color(0xFFFFFFFF)
|
||||
val md_theme_light_onErrorContainer = Color(0xFF410002)
|
||||
val md_theme_light_background = Color(0xFFFFFBFF)
|
||||
val md_theme_light_onBackground = Color(0xFF1C1B1F)
|
||||
val md_theme_light_surface = Color(0xFFFFFBFF)
|
||||
val md_theme_light_onSurface = Color(0xFF1C1B1F)
|
||||
val md_theme_light_surfaceVariant = Color(0xFFE5E1EC)
|
||||
val md_theme_light_onSurfaceVariant = Color(0xFF47464F)
|
||||
val md_theme_light_outline = Color(0xFF787680)
|
||||
val md_theme_light_inverseOnSurface = Color(0xFFF4EFF4)
|
||||
val md_theme_light_inverseSurface = Color(0xFF313034)
|
||||
val md_theme_light_inversePrimary = Color(0xFFC5C0FF)
|
||||
val md_theme_light_shadow = Color(0xFF000000)
|
||||
val md_theme_light_surfaceTint = Color(0xFF5951B3)
|
||||
val md_theme_light_outlineVariant = Color(0xFFC8C5D0)
|
||||
val md_theme_light_scrim = Color(0xFF000000)
|
||||
|
||||
// Dark Mode
|
||||
val md_theme_dark_primary = Color(0xFFC5C0FF)
|
||||
val md_theme_dark_onPrimary = Color(0xFF2A1D82)
|
||||
val md_theme_dark_primaryContainer = Color(0xFF413899)
|
||||
val md_theme_dark_onPrimaryContainer = Color(0xFFE4DFFF)
|
||||
val md_theme_dark_secondary = Color(0xFFC8C4DC)
|
||||
val md_theme_dark_onSecondary = Color(0xFF302E41)
|
||||
val md_theme_dark_secondaryContainer = Color(0xFF464559)
|
||||
val md_theme_dark_onSecondaryContainer = Color(0xFFE4DFF9)
|
||||
val md_theme_dark_tertiary = Color(0xFFC3C0FF)
|
||||
val md_theme_dark_onTertiary = Color(0xFF282377)
|
||||
val md_theme_dark_tertiaryContainer = Color(0xFF3F3C8F)
|
||||
val md_theme_dark_onTertiaryContainer = Color(0xFFE3DFFF)
|
||||
val md_theme_dark_error = Color(0xFFFFB4AB)
|
||||
val md_theme_dark_errorContainer = Color(0xFF93000A)
|
||||
val md_theme_dark_onError = Color(0xFF690005)
|
||||
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
|
||||
val md_theme_dark_background = Color(0xFF1C1B1F)
|
||||
val md_theme_dark_onBackground = Color(0xFFE5E1E6)
|
||||
val md_theme_dark_surface = Color(0xFF1C1B1F)
|
||||
val md_theme_dark_onSurface = Color(0xFFE5E1E6)
|
||||
val md_theme_dark_surfaceVariant = Color(0xFF47464F)
|
||||
val md_theme_dark_onSurfaceVariant = Color(0xFFC8C5D0)
|
||||
val md_theme_dark_outline = Color(0xFF928F99)
|
||||
val md_theme_dark_inverseOnSurface = Color(0xFF1C1B1F)
|
||||
val md_theme_dark_inverseSurface = Color(0xFFE5E1E6)
|
||||
val md_theme_dark_inversePrimary = Color(0xFF5951B3)
|
||||
val md_theme_dark_shadow = Color(0xFF000000)
|
||||
val md_theme_dark_surfaceTint = Color(0xFFC5C0FF)
|
||||
val md_theme_dark_outlineVariant = Color(0xFF47464F)
|
||||
val md_theme_dark_scrim = Color(0xFF000000)
|
||||
|
||||
|
||||
val seed = Color(0xFF5E56B8)
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.illusionware.npsbrowser.ui.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = md_theme_light_primary,
|
||||
onPrimary = md_theme_light_onPrimary,
|
||||
primaryContainer = md_theme_light_primaryContainer,
|
||||
onPrimaryContainer = md_theme_light_onPrimaryContainer,
|
||||
secondary = md_theme_light_secondary,
|
||||
onSecondary = md_theme_light_onSecondary,
|
||||
secondaryContainer = md_theme_light_secondaryContainer,
|
||||
onSecondaryContainer = md_theme_light_onSecondaryContainer,
|
||||
tertiary = md_theme_light_tertiary,
|
||||
onTertiary = md_theme_light_onTertiary,
|
||||
tertiaryContainer = md_theme_light_tertiaryContainer,
|
||||
onTertiaryContainer = md_theme_light_onTertiaryContainer,
|
||||
error = md_theme_light_error,
|
||||
errorContainer = md_theme_light_errorContainer,
|
||||
onError = md_theme_light_onError,
|
||||
onErrorContainer = md_theme_light_onErrorContainer,
|
||||
background = md_theme_light_background,
|
||||
onBackground = md_theme_light_onBackground,
|
||||
surface = md_theme_light_surface,
|
||||
onSurface = md_theme_light_onSurface,
|
||||
surfaceVariant = md_theme_light_surfaceVariant,
|
||||
onSurfaceVariant = md_theme_light_onSurfaceVariant,
|
||||
outline = md_theme_light_outline,
|
||||
inverseOnSurface = md_theme_light_inverseOnSurface,
|
||||
inverseSurface = md_theme_light_inverseSurface,
|
||||
inversePrimary = md_theme_light_inversePrimary,
|
||||
surfaceTint = md_theme_light_surfaceTint,
|
||||
outlineVariant = md_theme_light_outlineVariant,
|
||||
scrim = md_theme_light_scrim,
|
||||
)
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = md_theme_dark_primary,
|
||||
onPrimary = md_theme_dark_onPrimary,
|
||||
primaryContainer = md_theme_dark_primaryContainer,
|
||||
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
|
||||
secondary = md_theme_dark_secondary,
|
||||
onSecondary = md_theme_dark_onSecondary,
|
||||
secondaryContainer = md_theme_dark_secondaryContainer,
|
||||
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
|
||||
tertiary = md_theme_dark_tertiary,
|
||||
onTertiary = md_theme_dark_onTertiary,
|
||||
tertiaryContainer = md_theme_dark_tertiaryContainer,
|
||||
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
|
||||
error = md_theme_dark_error,
|
||||
errorContainer = md_theme_dark_errorContainer,
|
||||
onError = md_theme_dark_onError,
|
||||
onErrorContainer = md_theme_dark_onErrorContainer,
|
||||
background = md_theme_dark_background,
|
||||
onBackground = md_theme_dark_onBackground,
|
||||
surface = md_theme_dark_surface,
|
||||
onSurface = md_theme_dark_onSurface,
|
||||
surfaceVariant = md_theme_dark_surfaceVariant,
|
||||
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
|
||||
outline = md_theme_dark_outline,
|
||||
inverseOnSurface = md_theme_dark_inverseOnSurface,
|
||||
inverseSurface = md_theme_dark_inverseSurface,
|
||||
inversePrimary = md_theme_dark_inversePrimary,
|
||||
surfaceTint = md_theme_dark_surfaceTint,
|
||||
outlineVariant = md_theme_dark_outlineVariant,
|
||||
scrim = md_theme_dark_scrim,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun NPSBrowserTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
window.statusBarColor = Color.Transparent.toArgb()
|
||||
// allows pages to extend to the status bar
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
|
||||
systemUiController.setSystemBarsColor(
|
||||
color = Color.Transparent,
|
||||
darkIcons = !darkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.illusionware.npsbrowser.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
|
||||
)
|
||||
*/
|
||||
)
|
||||
Reference in New Issue
Block a user