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:
+13
-27
@@ -4,8 +4,6 @@ import java.io.FileInputStream
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
id("kotlin-parcelize")
|
||||
id("kotlin-kapt")
|
||||
}
|
||||
|
||||
val keystoreProperties = Properties()
|
||||
@@ -32,7 +30,6 @@ android {
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
compose = true
|
||||
|
||||
aidl = false
|
||||
@@ -52,7 +49,8 @@ android {
|
||||
buildTypes {
|
||||
named("release") {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
isMinifyEnabled = false
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
@@ -69,12 +67,6 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -83,8 +75,8 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")
|
||||
implementation("androidx.activity:activity-compose:1.5.1")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
||||
implementation("androidx.activity:activity-compose:1.7.2")
|
||||
implementation(platform("androidx.compose:compose-bom:2022.10.00"))
|
||||
implementation("androidx.compose.ui:ui")
|
||||
implementation("androidx.compose.ui:ui-graphics")
|
||||
@@ -96,32 +88,26 @@ dependencies {
|
||||
androidTestImplementation(composeBom)
|
||||
|
||||
// Material 3
|
||||
implementation("androidx.compose.material3:material3")
|
||||
implementation("androidx.compose.material3:material3:1.1.0")
|
||||
implementation("androidx.compose.material:material-icons-extended:1.4.3")
|
||||
// Android Studio Preview support
|
||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.core:core-ktx:1.10.1")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
|
||||
// Preferences DataStore (alternative to SharedPreferences)
|
||||
implementation("androidx.datastore:datastore-preferences:1.0.0")
|
||||
|
||||
// Compose extras library for controlling system ui and etc.
|
||||
implementation("com.google.accompanist:accompanist-systemuicontroller:0.31.2-alpha")
|
||||
|
||||
implementation("com.github.doyaaaaaken:kotlin-csv-jvm:1.8.0")
|
||||
implementation("com.github.wrdlbrnft:sorted-list-adapter:0.3.0.27")
|
||||
implementation("androidx.cardview:cardview:1.0.0")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
|
||||
implementation("androidx.preference:preference-ktx:1.2.0")
|
||||
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
|
||||
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
implementation("com.google.android.material:material:1.9.0")
|
||||
implementation("com.github.bumptech.glide:glide:4.14.2")
|
||||
annotationProcessor("com.github.bumptech.glide:compiler:4.14.2")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
implementation("androidx.navigation:navigation-compose:2.6.0")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<path
|
||||
android:pathData="M208,76L100,76L100,56a28,28 0,0 1,28 -28c13.51,0 25.65,9.62 28.24,22.39a12,12 0,1 0,23.52 -4.78C174.87,21.5 153.1,4 128,4A52.06,52.06 0,0 0,76 56L76,76L48,76A20,20 0,0 0,28 96L28,208a20,20 0,0 0,20 20L208,228a20,20 0,0 0,20 -20L228,96A20,20 0,0 0,208 76ZM204,204L52,204L52,100L204,100ZM112,152a16,16 0,1 1,16 16A16,16 0,0 1,112 152Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,118 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="51.512dp"
|
||||
android:height="31.41dp"
|
||||
android:viewportWidth="51.512"
|
||||
android:viewportHeight="31.41">
|
||||
<path
|
||||
android:pathData="m14.423,19.84v-8.27h2.22c1,1.85 2.94,5.2 3.32,6.1v0c-0.09,-0.86 -0.09,-2.29 -0.09,-3.65v-2.45h1.62v8.27h-2.12c-0.89,-1.62 -3,-5.47 -3.42,-6.42v0c0.06,0.72 0.08,2.42 0.08,3.9v2.52z"
|
||||
android:fillColor="#fff"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m23.582,11.57h3.42c1.76,0 3,1 3,2.56 0,1.79 -1.36,2.62 -3,2.62h-1.63v3.09h-1.79zM25.372,15.33h1.37c0.82,0 1.41,-0.32 1.41,-1.17 0,-0.85 -0.62,-1.17 -1.38,-1.17h-1.4z"
|
||||
android:fillColor="#fff"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m32.592,17.435a1.54,1.54 0,0 0,1.74 1.2c1,0 1.37,-0.38 1.37,-1 0,-0.62 -0.4,-1 -1.82,-1.3 -2.25,-0.53 -2.8,-1.35 -2.8,-2.46 0,-1.44 1.07,-2.42 3,-2.42 2.2,0 3.07,1.18 3.19,2.39h-1.81c-0.09,-0.51 -0.37,-1.06 -1.43,-1.06 -0.73,0 -1.17,0.3 -1.17,0.88 0,0.58 0.35,0.81 1.69,1.12 2.4,0.57 2.93,1.46 2.93,2.63 0,1.51 -1.14,2.54 -3.33,2.54 -2.19,0 -3.2,-1 -3.4,-2.54z"
|
||||
android:fillColor="#fff"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m10.951,3.846v0.75z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m3.621,13.07h1.54,0.06l0.71,0.73a0.14,0.14 0,0 1,0 0.19l-0.71,0.73a0.09,0.09 0,0 1,-0.06 0h-1.54a0.1,0.1 0,0 1,-0.09 -0.11v-1.43a0.1,0.1 0,0 1,0.09 -0.11z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m3.603,12.694 l-0.018,0.002c-0.258,0.025 -0.455,0.265 -0.428,0.523l-0.002,-0.039v1.43l0.002,-0.039c-0.027,0.258 0.169,0.497 0.428,0.521l0.018,0.002h1.559l-0.125,-0.021c0.1,0.035 0.21,0.035 0.311,0l0.082,-0.029 0.773,-0.795 0.004,-0.004c0.18,-0.195 0.18,-0.504 0,-0.699l-0.004,-0.004 -0.822,-0.848h-0.219zM3.905,13.444h1.156l0.438,0.451 -0.438,0.449h-1.156z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m5.891,17.05v-1.54a0.05,0.05 0,0 1,0 -0.05l0.73,-0.72a0.17,0.17 0,0 1,0.19 0l0.74,0.72a0.08,0.08 0,0 1,0 0.05v1.54a0.11,0.11 0,0 1,-0.11 0.1h-1.43a0.11,0.11 0,0 1,-0.12 -0.1z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m6.411,14.429 l-0.029,0.02 -0.791,0.781 -0.025,0.043c-0.076,0.131 -0.076,0.295 0,0.426l-0.049,-0.188v1.557,0.016c0.023,0.264 0.266,0.466 0.529,0.441h1.395c0.247,0 0.462,-0.193 0.484,-0.439v-0.018,-1.557l-0.018,0.117c0.03,-0.092 0.03,-0.193 0,-0.285l-0.029,-0.088 -0.83,-0.807 -0.027,-0.02c-0.183,-0.123 -0.426,-0.123 -0.609,0zM6.718,15.171 L7.175,15.618v1.156h-0.908v-1.158z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m9.871,14.78h-1.53a0.06,0.06 0,0 1,-0.06 0L7.561,14.05a0.17,0.17 0,0 1,0 -0.19l0.72,-0.73a0.06,0.06 0,0 1,0.06 0h1.53a0.1,0.1 0,0 1,0.1 0.11L9.971,14.7a0.1,0.1 0,0 1,-0.1 0.08z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m8.341,12.755 l0.188,0.051c-0.134,-0.077 -0.301,-0.077 -0.436,0l-0.043,0.025 -0.781,0.791 -0.02,0.029c-0.123,0.183 -0.123,0.424 0,0.607l0.02,0.029 0.781,0.791 0.043,0.025c0.134,0.077 0.301,0.077 0.436,0l-0.188,0.051h1.521c0.227,0.005 0.431,-0.159 0.477,-0.381l0.008,-0.037v-1.498l-0.002,0.039c0.028,-0.275 -0.201,-0.525 -0.477,-0.523zM8.439,13.505h1.158v0.9h-1.158l-0.445,-0.451z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m7.601,10.8v1.54a0.14,0.14 0,0 1,0 0.06l-0.74,0.71a0.14,0.14 0,0 1,-0.19 0L5.941,12.4a0.09,0.09 0,0 1,0 -0.06L5.941,10.8c0,-0.05 0,-0.09 0.12,-0.09h1.48a0.1,0.1 0,0 1,0.06 0.09z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m6.062,10.335c-0.102,0 -0.249,0.004 -0.381,0.146 -0.132,0.143 -0.115,0.293 -0.115,0.318v1.541l0.021,-0.125c-0.035,0.1 -0.035,0.208 0,0.309l0.029,0.084 0.797,0.773 0.004,0.004c0.195,0.18 0.502,0.18 0.697,0l0.004,-0.002 0.824,-0.791 0.025,-0.111c0.016,-0.073 0.016,-0.148 0,-0.221l0.008,0.08v-1.543,-0.004c-0.003,-0.185 -0.115,-0.354 -0.285,-0.428l-0.072,-0.031zM6.316,11.085h0.91v1.156l-0.459,0.439 -0.451,-0.439z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m42.812,19.171c-1.007,0 -1.83,0.823 -1.83,1.83 0,1.007 0.823,1.83 1.83,1.83 1.007,0 1.83,-0.823 1.83,-1.83 0,-1.007 -0.823,-1.83 -1.83,-1.83zM42.812,19.77c0.683,0 1.229,0.548 1.229,1.23 0,0.683 -0.546,1.229 -1.229,1.229 -0.683,0 -1.23,-0.546 -1.23,-1.229 0,-0.683 0.548,-1.23 1.23,-1.23z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m8.71,19.171c-1.007,0 -1.83,0.823 -1.83,1.83 0,1.007 0.823,1.83 1.83,1.83 1.007,0 1.83,-0.823 1.83,-1.83 0,-1.007 -0.823,-1.83 -1.83,-1.83zM8.71,19.77c0.683,0 1.23,0.548 1.23,1.23 0,0.683 -0.548,1.229 -1.23,1.229 -0.683,0 -1.229,-0.546 -1.229,-1.229 0,-0.683 0.546,-1.23 1.229,-1.23z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m43.441,14.02a1.05,1.05 0,0 1,-1.05 1.05,1.05 1.05,0 0,1 -1.05,-1.05 1.05,1.05 0,0 1,1.05 -1.05,1.05 1.05,0 0,1 1.05,1.05z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m42.392,12.595c-0.783,0 -1.426,0.643 -1.426,1.426 0,0.783 0.643,1.424 1.426,1.424 0.783,0 1.424,-0.641 1.424,-1.424 0,-0.783 -0.641,-1.426 -1.424,-1.426zM42.392,13.345c0.377,0 0.674,0.299 0.674,0.676 0,0.377 -0.297,0.674 -0.674,0.674 -0.377,0 -0.676,-0.297 -0.676,-0.674 0,-0.377 0.299,-0.676 0.676,-0.676z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m45.941,16.52a1.05,1.05 0,0 1,-1.05 1.05,1.05 1.05,0 0,1 -1.05,-1.05 1.05,1.05 0,0 1,1.05 -1.05,1.05 1.05,0 0,1 1.05,1.05z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m44.892,15.095c-0.783,0 -1.426,0.643 -1.426,1.426 0,0.783 0.643,1.424 1.426,1.424 0.783,0 1.424,-0.641 1.424,-1.424 0,-0.783 -0.641,-1.426 -1.424,-1.426zM44.892,15.845c0.377,0 0.674,0.299 0.674,0.676 0,0.377 -0.297,0.674 -0.674,0.674 -0.377,0 -0.676,-0.297 -0.676,-0.674 0,-0.377 0.299,-0.676 0.676,-0.676z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m48.441,14.02a1.05,1.05 0,0 1,-1.05 1.05,1.05 1.05,0 0,1 -1.05,-1.05 1.05,1.05 0,0 1,1.05 -1.05,1.05 1.05,0 0,1 1.05,1.05z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m47.392,12.595c-0.783,0 -1.426,0.643 -1.426,1.426 0,0.783 0.643,1.424 1.426,1.424 0.783,0 1.424,-0.641 1.424,-1.424 0,-0.783 -0.641,-1.426 -1.424,-1.426zM47.392,13.345c0.377,0 0.674,0.299 0.674,0.676 0,0.377 -0.297,0.674 -0.674,0.674 -0.377,0 -0.676,-0.297 -0.676,-0.674 0,-0.377 0.299,-0.676 0.676,-0.676z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m45.941,11.52a1.05,1.05 0,0 1,-1.05 1.05,1.05 1.05,0 0,1 -1.05,-1.05 1.05,1.05 0,0 1,1.05 -1.05,1.05 1.05,0 0,1 1.05,1.05z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m44.892,10.095c-0.783,0 -1.426,0.643 -1.426,1.426 0,0.783 0.643,1.424 1.426,1.424 0.783,0 1.424,-0.641 1.424,-1.424 0,-0.783 -0.641,-1.426 -1.424,-1.426zM44.892,10.845c0.377,0 0.674,0.299 0.674,0.676 0,0.377 -0.297,0.674 -0.674,0.674 -0.377,0 -0.676,-0.297 -0.676,-0.674 0,-0.377 0.299,-0.676 0.676,-0.676z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m10.99,7.32v0.5,16.26h29.531L40.521,7.32ZM11.99,8.32h27.531v14.76h-27.531z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M12.201,3.5C5.454,3.5 0,8.952 0,15.699v0.012c0,6.748 5.454,12.199 12.201,12.199h27.109c6.748,0 12.201,-5.452 12.201,-12.199v-0.012C51.512,8.952 46.058,3.5 39.311,3.5ZM12.201,4.801h27.109c6.05,0 10.9,4.849 10.9,10.898v0.012c0,6.05 -4.851,10.898 -10.9,10.898L12.201,26.609c-6.05,0 -10.9,-4.849 -10.9,-10.898v-0.012c0,-6.05 4.851,-10.898 10.9,-10.898z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m5.637,24.529 l-0.012,1 4.623,0.051h0.309c-0.109,-0.014 0.023,0.022 0.158,0.186 0.138,0.167 0.298,0.413 0.441,0.66 0.287,0.495 0.508,0.988 0.508,0.988l0.912,-0.408c0,0 -0.236,-0.532 -0.555,-1.082 -0.159,-0.275 -0.337,-0.555 -0.535,-0.795 -0.198,-0.24 -0.383,-0.488 -0.797,-0.545l-0.035,-0.004h-0.4z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m11.664,4.025c0,0 -0.221,0.493 -0.508,0.988 -0.143,0.247 -0.303,0.495 -0.441,0.662 -0.135,0.163 -0.268,0.2 -0.158,0.186 -0.088,0.006 -0.176,0.007 -0.264,0l-0.023,-0.002 -4.645,0.051 0.012,1 4.619,-0.051 -0.047,-0.002c0.151,0.013 0.302,0.013 0.453,0h0.014l0.014,-0.002c0.414,-0.057 0.599,-0.303 0.797,-0.543 0.198,-0.24 0.376,-0.522 0.535,-0.797 0.318,-0.55 0.555,-1.08 0.555,-1.08z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m45.756,24.529 l-4.627,0.051h-0.393l-0.033,0.004c-0.412,0.056 -0.602,0.305 -0.801,0.545 -0.199,0.24 -0.376,0.522 -0.535,0.797 -0.318,0.551 -0.553,1.082 -0.553,1.082l0.914,0.404c0,0 0.219,-0.492 0.504,-0.986 0.142,-0.247 0.302,-0.494 0.439,-0.66 0.135,-0.163 0.266,-0.199 0.162,-0.186h0.299l4.633,-0.051z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m39.729,4.027 l-0.914,0.406c0,0 0.235,0.531 0.553,1.082 0.159,0.275 0.336,0.557 0.535,0.797 0.199,0.24 0.388,0.487 0.801,0.543l0.012,0.002h0.014c0.148,0.013 0.297,0.013 0.445,0l-0.049,0.002 4.631,0.051 0.01,-1 -4.654,-0.051 -0.023,0.002c-0.085,0.007 -0.169,0.006 -0.254,0 0.104,0.013 -0.027,-0.023 -0.162,-0.186C40.534,5.509 40.375,5.263 40.232,5.016 39.947,4.521 39.729,4.027 39.729,4.027Z"
|
||||
android:fillColor="#2f2f2f"
|
||||
android:strokeColor="#00000000"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<path
|
||||
android:pathData="M128,24A104,104 0,1 0,232 128,104.11 104.11,0 0,0 128,24ZM128,216a88,88 0,1 1,88 -88A88.1,88.1 0,0 1,128 216ZM144,176a8,8 0,0 1,-8 8,16 16,0 0,1 -16,-16L120,128a8,8 0,0 1,0 -16,16 16,0 0,1 16,16v40A8,8 0,0 1,144 176ZM112,84a12,12 0,1 1,12 12A12,12 0,0 1,112 84Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="67.733dp"
|
||||
android:height="67.733dp"
|
||||
android:viewportWidth="67.733"
|
||||
android:viewportHeight="67.733">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M56.541,21.775 L41.725,6.959c-0.403,-0.393 -0.945,-0.612 -1.508,-0.609L14.817,6.35c-2.338,0 -4.233,1.895 -4.233,4.233v23.283c-0,1.169 0.948,2.117 2.117,2.117h42.333c1.169,0 2.117,-0.948 2.117,-2.117v-10.583c0.003,-0.563 -0.215,-1.105 -0.609,-1.508zM40.217,23.283v-11.642l11.642,11.642zM24.342,44.45c0,1.169 -0.948,2.117 -2.117,2.117h-2.646v10.583c0,2.822 -4.233,2.822 -4.233,0v-10.583h-2.646c-2.818,-0.004 -2.818,-4.229 0,-4.233h9.525c1.169,0 2.117,0.948 2.117,2.117zM40.217,53.975c0,3.466 -2.461,5.556 -6.615,5.556 -1.921,-0.006 -3.788,-0.637 -5.318,-1.799v0c-0.501,-0.394 -0.786,-1.003 -0.767,-1.64 -0,-1.169 0.948,-2.117 2.117,-2.117 0.49,0.004 0.965,0.171 1.349,0.476h-0.026c0.781,0.533 1.7,0.827 2.646,0.847 2.381,0 2.381,-0.794 2.381,-1.323 0,-0.529 0,-0.238 -0.317,-0.423 -0.317,-0.185 -1.535,-0.582 -2.434,-0.82 -2.275,-0.609 -5.715,-1.535 -5.715,-5.106 0,-3.572 2.514,-5.556 6.085,-5.556 1.921,0.006 3.788,0.637 5.318,1.799v0c0.501,0.394 0.786,1.003 0.767,1.64 0,1.169 -0.948,2.117 -2.117,2.117 -0.49,-0.004 -0.965,-0.171 -1.349,-0.476h0.026c-0.781,-0.533 -1.7,-0.827 -2.646,-0.847 -1.217,0 -1.852,0.45 -1.852,1.323v0.053c0.291,0.344 1.746,0.741 2.566,0.952 2.196,0.582 5.9,1.561 5.9,5.345zM55.798,42.855c-1.081,-0.457 -2.328,0.054 -2.778,1.138l-3.334,8.017 -3.334,-8.017c-1.095,-2.606 -5.005,-0.968 -3.916,1.64l5.292,12.7c0.719,1.75 3.197,1.75 3.916,0l2.864,-6.874 2.427,-5.826c0.457,-1.081 -0.054,-2.328 -1.138,-2.778z"
|
||||
android:strokeWidth="0.264583"/>
|
||||
</vector>
|
||||
@@ -2,7 +2,8 @@
|
||||
<resources>
|
||||
<string name="app_name">NPS Browser</string>
|
||||
<string name="title_activity_settings">设置</string>
|
||||
<string name="search">搜索</string>
|
||||
<string name="search">搜索…</string>
|
||||
<string name="search_tooltip">搜索</string>
|
||||
<string name="download">下载</string>
|
||||
<string name="copy_zrif">复制zRIF</string>
|
||||
<string name="content_id">内容ID</string>
|
||||
@@ -15,7 +16,7 @@
|
||||
<string name="download_error">下载过程中发生错误,请重试</string>
|
||||
<string name="pkg_url_copied">PKG地址已复制到剪贴板</string>
|
||||
<string name="zrif_copied">zRIF已复制到剪贴板</string>
|
||||
<string name="downloading">下载中...</string>
|
||||
<string name="downloading">下载中…</string>
|
||||
<string name="content_id_copied">内容ID已复制到剪贴板</string>
|
||||
<string name="appearance">外观</string>
|
||||
<string name="theme">主题</string>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<resources>
|
||||
<string name="app_name">NPS Browser</string>
|
||||
<string name="title_activity_settings">設置</string>
|
||||
<string name="search">搜尋</string>
|
||||
<string name="search">搜尋…</string>
|
||||
<string name="search_tooltip">搜尋</string>
|
||||
<string name="download">下載</string>
|
||||
<string name="copy_zrif">複製zRIF</string>
|
||||
<string name="content_id">內容ID</string>
|
||||
@@ -15,7 +16,7 @@
|
||||
<string name="download_error">下載過程中發生錯誤,請重試</string>
|
||||
<string name="pkg_url_copied">PKG地址已複製到剪貼板</string>
|
||||
<string name="zrif_copied">zRIF已複製到剪貼板</string>
|
||||
<string name="downloading">正在下載...</string>
|
||||
<string name="downloading">正在下載…</string>
|
||||
<string name="content_id_copied">內容ID已複製到剪貼板</string>
|
||||
<string name="appearance">外觀</string>
|
||||
<string name="theme">主題</string>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="app_theme_val">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</string-array>
|
||||
<string-array name="layout_type_val">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -2,7 +2,8 @@
|
||||
<resources>
|
||||
<string name="app_name">NPS Browser</string>
|
||||
<string name="title_activity_settings">Settings</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="search">Search…</string>
|
||||
<string name="search_tooltip">Search</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="copy_zrif">Copy zRIF</string>
|
||||
<string name="content_id">Content ID</string>
|
||||
@@ -12,7 +13,7 @@
|
||||
<string name="no_license">Warning: No license was provided for this content.</string>
|
||||
<string name="cart_only">CART ONLY GAME</string>
|
||||
<string name="no_download_link">Warning: No download link was provided for this content.</string>
|
||||
<string name="downloading">Downloading...</string>
|
||||
<string name="downloading">Downloading…</string>
|
||||
<string name="download_error">An error occurred while downloading. please try again</string>
|
||||
<string name="pkg_url_copied">PKG URL Copied to Clipboard</string>
|
||||
<string name="zrif_copied">zRIF Copied to Clipboard</string>
|
||||
@@ -31,5 +32,4 @@
|
||||
<item>List</item>
|
||||
<item>Grid</item>
|
||||
</string-array>
|
||||
<string name="title_activity_thing">thing</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="NPSTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<style name="NPSTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryVariant">@color/colorPrimaryDark</item>
|
||||
@@ -14,7 +14,7 @@
|
||||
<item name="alertDialogTheme">@style/NPSTheme.AlertDialog</item>
|
||||
</style>
|
||||
|
||||
<style name="NPSTheme.AlertDialog" parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
|
||||
<style name="NPSTheme.AlertDialog" parent="Theme.Material3.DayNight.Dialog.Alert">
|
||||
<item name="colorPrimary">@color/colorOnPrimary</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
@@ -34,13 +34,9 @@
|
||||
<item name="windowSplashScreenBackground">@color/colorAccent</item>
|
||||
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_nps_fg_no_shadow</item>
|
||||
<item name="windowSplashScreenAnimationDuration">500</item>
|
||||
<item name="windowSplashScreenAnimationDuration">800</item>
|
||||
|
||||
<item name="postSplashScreenTheme">@style/NPSTheme</item>
|
||||
</style>
|
||||
|
||||
<style name="NPSTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<style name="NPSTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<PreferenceCategory android:title="@string/appearance">
|
||||
|
||||
<com.illusionware.npsbrowser.ThemePreference
|
||||
android:defaultValue="2"
|
||||
android:entries="@array/app_theme"
|
||||
android:entryValues="@array/app_theme_val"
|
||||
android:key="app_theme"
|
||||
android:title="@string/theme" />
|
||||
<ListPreference
|
||||
android:defaultValue="0"
|
||||
android:entries="@array/layout_type"
|
||||
android:entryValues="@array/layout_type_val"
|
||||
android:key="layout_type"
|
||||
android:title="@string/layout"
|
||||
app:useSimpleSummaryProvider="true"/>
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
@@ -6,7 +6,6 @@ buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:8.0.2")
|
||||
@@ -21,7 +20,6 @@ allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@ org.gradle.jvmargs=-Xmx1536m
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
#Tue Oct 20 12:27:05 GMT+03:00 2020
|
||||
#Wed Jun 21 06:42:34 EEST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
|
||||
|
||||
Reference in New Issue
Block a user