mirror of
https://github.com/IllusionMan1212/NPS-Browser.git
synced 2026-10-24 18:12:38 +00:00
Compare commits
1
Commits
2178a0c9a4
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d754530c0b |
@@ -0,0 +1,87 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-parcelize'
|
||||
id 'kotlin-kapt'
|
||||
}
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('keystore.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.illusionware.npsbrowser'
|
||||
compileSdkVersion 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.illusionware.npsbrowser"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 33
|
||||
versionCode 1
|
||||
versionName "0.1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
dataBinding = true
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
buildToolsVersion '33.0.1'
|
||||
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.6.0'
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||
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.5.3'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
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.8.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"
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import java.util.Properties
|
||||
import java.io.FileInputStream
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
}
|
||||
|
||||
val keystoreProperties = Properties()
|
||||
val keystorePropertiesFile = rootProject.file("keystore.properties")
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.illusionware.npsbrowser"
|
||||
compileSdk = 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.illusionware.npsbrowser"
|
||||
minSdk = 23
|
||||
targetSdk = 33
|
||||
versionCode = 1
|
||||
versionName = "0.1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
|
||||
aidl = false
|
||||
renderScript = false
|
||||
shaders = false
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
keyAlias = keystoreProperties["keyAlias"] as String?
|
||||
keyPassword = keystoreProperties["keyPassword"] as String?
|
||||
storeFile = keystoreProperties["storeFile"]?.let { file(it) }
|
||||
storePassword = keystoreProperties["storePassword"] as String?
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
named("release") {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.4.8"
|
||||
}
|
||||
buildToolsVersion = "33.0.1"
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-compose: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")
|
||||
implementation("androidx.core:core-ktx:1.10.1")
|
||||
androidTestImplementation(platform("androidx.compose:compose-bom:2022.10.00"))
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||
// Jetpack Compose
|
||||
val composeBom = platform("androidx.compose:compose-bom:2023.04.01")
|
||||
implementation(composeBom)
|
||||
androidTestImplementation(composeBom)
|
||||
|
||||
// Material 3
|
||||
implementation("androidx.compose.material3:material3:1.1.1")
|
||||
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")
|
||||
|
||||
// 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.IllusionMan1212:kotlin-csv:cedb7c5ab2")
|
||||
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")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
implementation("androidx.navigation:navigation-compose:2.6.0")
|
||||
implementation("io.coil-kt:coil:2.4.0")
|
||||
implementation("io.coil-kt:coil-compose:2.4.0")
|
||||
}
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.kts.
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
@@ -7,26 +7,23 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name=".NPSApp"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/NPSTheme"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:configChanges="screenSize|screenLayout|keyboardHidden|orientation"
|
||||
android:exported="true"
|
||||
android:theme="@style/NPSTheme.Splash"
|
||||
>
|
||||
android:theme="@style/NPSTheme">
|
||||
<activity android:name=".activities.SplashActivity" android:theme="@style/NPSTheme.Splash"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".activities.MainActivity"
|
||||
android:configChanges="screenSize|screenLayout|keyboardHidden|orientation">
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,61 @@
|
||||
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 version: String?, 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 (version != other.version) 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 + (version?.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
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.illusionware.npsbrowser
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.illusionware.npsbrowser.data.OnboardingPreferencesRepository
|
||||
import com.illusionware.npsbrowser.data.SettingsPreferencesRepository
|
||||
|
||||
private const val PREFERENCES_NAME = "preferences"
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
|
||||
name = PREFERENCES_NAME
|
||||
)
|
||||
|
||||
class NPSApp: Application() {
|
||||
lateinit var settingsPreferencesRepository: SettingsPreferencesRepository
|
||||
lateinit var onboardingPreferencesRepository: OnboardingPreferencesRepository
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
settingsPreferencesRepository = SettingsPreferencesRepository(dataStore)
|
||||
onboardingPreferencesRepository = OnboardingPreferencesRepository(dataStore)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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,8 +0,0 @@
|
||||
package com.illusionware.npsbrowser
|
||||
|
||||
sealed class Routes(val route: String) {
|
||||
object Home : Routes("home")
|
||||
object Onboarding : Routes("onboarding")
|
||||
object Settings : Routes("settings")
|
||||
object PackageDetails : Routes("package_details")
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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,711 +1,55 @@
|
||||
package com.illusionware.npsbrowser.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.material.icons.filled.ViewList
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.ProvideTextStyle
|
||||
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.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.ColorPainter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import android.view.MenuItem
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.illusionware.npsbrowser.R
|
||||
import com.illusionware.npsbrowser.Routes
|
||||
import com.illusionware.npsbrowser.data.ItemLayout
|
||||
import com.illusionware.npsbrowser.data.SettingsPreferences
|
||||
import com.illusionware.npsbrowser.model.ConsoleType
|
||||
import com.illusionware.npsbrowser.model.PackageItem
|
||||
import com.illusionware.npsbrowser.ui.components.*
|
||||
import com.illusionware.npsbrowser.ui.screens.OnBoarding
|
||||
import com.illusionware.npsbrowser.ui.screens.PackageDetails
|
||||
import com.illusionware.npsbrowser.ui.screens.SettingsScreen
|
||||
import com.illusionware.npsbrowser.ui.theme.ColorAccent
|
||||
import com.illusionware.npsbrowser.ui.theme.ColorOnPrimaryLight
|
||||
import com.illusionware.npsbrowser.ui.theme.NPSBrowserTheme
|
||||
import com.illusionware.npsbrowser.ui.theme.Typography
|
||||
import com.illusionware.npsbrowser.util.isNavigationBarNeedsScrim
|
||||
import com.illusionware.npsbrowser.viewmodels.OnboardingViewModel
|
||||
import com.illusionware.npsbrowser.viewmodels.PackageDetailsViewModel
|
||||
import com.illusionware.npsbrowser.viewmodels.PackageListViewModel
|
||||
import com.illusionware.npsbrowser.viewmodels.SettingsViewModel
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import com.illusionware.npsbrowser.fragments.mainactivity.MainActivityFragment
|
||||
|
||||
val PlaceholderColor = Color(0x1F888888)
|
||||
val redHatDisplayFamily = FontFamily(
|
||||
Font(R.font.red_hat_display_bold, FontWeight.Bold),
|
||||
)
|
||||
class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener {
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
val settingsViewModel: SettingsViewModel = viewModel(
|
||||
factory = SettingsViewModel.Factory
|
||||
)
|
||||
val onboardingViewModel: OnboardingViewModel = viewModel(
|
||||
factory = OnboardingViewModel.Factory
|
||||
)
|
||||
val packageDetailsViewModel: PackageDetailsViewModel = viewModel()
|
||||
setTheme(R.style.NPSTheme)
|
||||
setContentView(R.layout.activity_main)
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
supportFragmentManager.addOnBackStackChangedListener(this);
|
||||
|
||||
val onboardingPrefs = onboardingViewModel.uiState.collectAsStateWithLifecycle().value
|
||||
val appTheme = runBlocking { settingsViewModel.getAppTheme() }
|
||||
val settingsPrefs = settingsViewModel.uiState.collectAsStateWithLifecycle().value
|
||||
val navController = rememberNavController()
|
||||
|
||||
val darkTheme = when (appTheme) {
|
||||
0 -> false
|
||||
1 -> true
|
||||
else -> isSystemInDarkTheme()
|
||||
if (supportFragmentManager.backStackEntryCount > 0) {
|
||||
showUpButton()
|
||||
}
|
||||
|
||||
NPSBrowserTheme(darkTheme = darkTheme) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = if (onboardingPrefs.seenOnboarding) MaterialTheme.colorScheme.background else ColorAccent
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = if (onboardingPrefs.seenOnboarding) Routes.Home.route else Routes.Onboarding.route,
|
||||
) {
|
||||
composable(Routes.Home.route) {
|
||||
HomePage(
|
||||
navigateToSettings = { navController.navigate(Routes.Settings.route) },
|
||||
settingsPrefs = settingsPrefs,
|
||||
settingsViewModel = settingsViewModel,
|
||||
navigateToDetailsScreen = { item ->
|
||||
packageDetailsViewModel.setSelectedPackage(item)
|
||||
navController.navigate(Routes.PackageDetails.route)
|
||||
},
|
||||
)
|
||||
}
|
||||
composable(Routes.Settings.route) {
|
||||
SettingsScreen(
|
||||
navigationGoBack = { navController.popBackStack() },
|
||||
)
|
||||
}
|
||||
composable(Routes.Onboarding.route) {
|
||||
OnBoarding(
|
||||
navigateToHome = {
|
||||
navController.popBackStack()
|
||||
navController.navigate(Routes.Home.route)
|
||||
},
|
||||
darkTheme = darkTheme,
|
||||
)
|
||||
}
|
||||
composable(Routes.PackageDetails.route) {
|
||||
PackageDetails(
|
||||
navigationGoBack = { navController.popBackStack() },
|
||||
packageDetailsViewModel = packageDetailsViewModel,
|
||||
)
|
||||
if (savedInstanceState == null) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, MainActivityFragment())
|
||||
.commitNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HomePage(
|
||||
navigateToSettings: () -> Unit,
|
||||
navigateToDetailsScreen: (item: PackageItem) -> Unit,
|
||||
settingsPrefs: SettingsPreferences,
|
||||
settingsViewModel: SettingsViewModel,
|
||||
) {
|
||||
var isSearchOpen by rememberSaveable { mutableStateOf(false)}
|
||||
var searchQuery by rememberSaveable { mutableStateOf("")}
|
||||
val searchBar = remember { FocusRequester() }
|
||||
var layoutDialogOpen by remember { mutableStateOf(false) }
|
||||
val context = LocalContext.current
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val navbarScrimColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp)
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
supportFragmentManager.popBackStack()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
val tsvs = settingsViewModel.availableTsvs()
|
||||
var selectedBarItem by rememberSaveable { mutableStateOf(ConsoleType.PSVITA) }
|
||||
|
||||
LaunchedEffect(systemUiController) {
|
||||
systemUiController.setNavigationBarColor(
|
||||
color = if (context.isNavigationBarNeedsScrim()) {
|
||||
navbarScrimColor.copy(alpha = 0.7f)
|
||||
override fun onBackStackChanged() {
|
||||
// enable Up button only if there are entries on the backstack
|
||||
if (supportFragmentManager.backStackEntryCount < 1) {
|
||||
hideUpButton()
|
||||
} else {
|
||||
Color.Transparent
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
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")
|
||||
showUpButton()
|
||||
}
|
||||
}
|
||||
|
||||
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 = if (settingsPrefs.layout == ItemLayout.GRID.ordinal) Icons.Filled.GridView else Icons.Filled.ViewList, 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
|
||||
))
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
if (tsvs.isNotEmpty()) {
|
||||
NavBar(
|
||||
selectedTab = selectedBarItem,
|
||||
onTabSelected = { selectedBarItem = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
Column(Modifier.padding(padding)) {
|
||||
if (tsvs.isEmpty()) {
|
||||
NoTsvs(navigateToSettings = navigateToSettings)
|
||||
} else {
|
||||
PackageList(
|
||||
navigateToDetailsScreen = navigateToDetailsScreen,
|
||||
type = selectedBarItem,
|
||||
settingsPrefs = settingsPrefs
|
||||
)
|
||||
}
|
||||
private fun showUpButton() {
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
if (layoutDialogOpen) {
|
||||
LayoutDialog(
|
||||
onDismiss = { layoutDialogOpen = false },
|
||||
prefs = settingsPrefs,
|
||||
viewModel = settingsViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavBar(selectedTab: ConsoleType, onTabSelected: (ConsoleType) -> Unit) {
|
||||
NavigationBar {
|
||||
NavigationBarItem(
|
||||
selected = selectedTab == ConsoleType.PSVITA,
|
||||
icon = { Icon(painterResource(id = R.drawable.vita_icon), contentDescription = "PS Vita") },
|
||||
label = { Text(text = "PS Vita") },
|
||||
onClick = {
|
||||
onTabSelected(ConsoleType.PSVITA)
|
||||
}
|
||||
)
|
||||
NavigationBarItem(
|
||||
selected = selectedTab == ConsoleType.PS3,
|
||||
icon = { Icon(painterResource(id = R.drawable.ps3_icon), contentDescription = "PS3") },
|
||||
label = { Text(text = "PS3") },
|
||||
onClick = {
|
||||
onTabSelected(ConsoleType.PS3)
|
||||
}
|
||||
)
|
||||
NavigationBarItem(
|
||||
selected = selectedTab == ConsoleType.PSP,
|
||||
icon = { Icon(painterResource(id = R.drawable.psp_icon), contentDescription = "PSP") },
|
||||
label = { Text(text = "PSP") },
|
||||
onClick = {
|
||||
onTabSelected(ConsoleType.PSP)
|
||||
}
|
||||
)
|
||||
NavigationBarItem(
|
||||
selected = selectedTab == ConsoleType.PSX,
|
||||
icon = { Icon(painterResource(id = R.drawable.psx_icon), contentDescription = "PSX") },
|
||||
label = { Text(text = "PSX") },
|
||||
onClick = {
|
||||
onTabSelected(ConsoleType.PSX)
|
||||
}
|
||||
)
|
||||
NavigationBarItem(
|
||||
selected = selectedTab == ConsoleType.PSM,
|
||||
icon = { Icon(painterResource(id = R.drawable.psm_icon), contentDescription = "PSM") },
|
||||
label = { Text(text = "PSM") },
|
||||
onClick = {
|
||||
onTabSelected(ConsoleType.PSM)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@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 PackageList(
|
||||
type: ConsoleType = ConsoleType.PSVITA,
|
||||
navigateToDetailsScreen: (item: PackageItem) -> Unit,
|
||||
viewModel: PackageListViewModel = viewModel(
|
||||
factory = PackageListViewModel.Factory
|
||||
),
|
||||
settingsPrefs: SettingsPreferences,
|
||||
) {
|
||||
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
|
||||
|
||||
val packages = when (type) {
|
||||
ConsoleType.PSVITA -> {
|
||||
uiState.packages.filter { it.consoleType == ConsoleType.PSVITA }
|
||||
}
|
||||
ConsoleType.PS3 -> {
|
||||
uiState.packages.filter { it.consoleType == ConsoleType.PS3 }
|
||||
}
|
||||
ConsoleType.PSP -> {
|
||||
uiState.packages.filter { it.consoleType == ConsoleType.PSP }
|
||||
}
|
||||
ConsoleType.PSX -> {
|
||||
uiState.packages.filter { it.consoleType == ConsoleType.PSX }
|
||||
}
|
||||
ConsoleType.PSM -> {
|
||||
uiState.packages.filter { it.consoleType == ConsoleType.PSM }
|
||||
}
|
||||
}
|
||||
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
if (uiState.isLoading) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
} else {
|
||||
if (packages.isEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
"You haven't added any TSVs for this console yet",
|
||||
style = Typography.bodyLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (settingsPrefs.layout == ItemLayout.LIST.ordinal) {
|
||||
LazyColumn {
|
||||
items(packages) {
|
||||
PackageListItem(it, navigateToDetailsScreen)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Adaptive(140.dp),
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
) {
|
||||
items(packages) {
|
||||
PackageGridItem(it, navigateToDetailsScreen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PackageListItem(
|
||||
item: PackageItem,
|
||||
navigateToDetailsScreen: (item: PackageItem) -> Unit,
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { navigateToDetailsScreen(item) }
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier.weight(1.0f)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data("TODO:")
|
||||
.crossfade(true)
|
||||
.error(R.drawable.default_game_icon)
|
||||
.build(),
|
||||
placeholder = ColorPainter(PlaceholderColor),
|
||||
contentDescription = "Package Icon",
|
||||
modifier = Modifier.size(48.dp),
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = item.name,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
style = Typography.bodyLarge,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 2,
|
||||
modifier = Modifier
|
||||
.width(IntrinsicSize.Min)
|
||||
.weight(1.0f)
|
||||
)
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
ProvideTextStyle(value = Typography.bodySmall.copy(color = MaterialTheme.colorScheme.outline)) {
|
||||
Text(text = item.region)
|
||||
if (!item.minFW.isNullOrEmpty()) {
|
||||
Text(text = item.minFW)
|
||||
}
|
||||
if (item.pkgSize.isNotEmpty()) {
|
||||
Text(text = item.pkgSize)
|
||||
}
|
||||
Tag(title = item.dataType.name, small = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Column {
|
||||
if (item.pkgUrl.isNullOrEmpty()) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.no_url_icon),
|
||||
contentDescription = "No PKG URL",
|
||||
tint = Color(0xFFCB5A5A)
|
||||
)
|
||||
}
|
||||
if (
|
||||
item.consoleType != ConsoleType.PSX &&
|
||||
(item.zRif.isNullOrEmpty() && item.rap.isNullOrEmpty())) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.no_key_icon),
|
||||
contentDescription = "No License Key",
|
||||
tint = Color(0xFFCB5A5A)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Tag(title: String, small: Boolean = false) {
|
||||
Box(
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.background(Color(0xFFEAF654))
|
||||
.layoutId("tag")
|
||||
.padding(horizontal = if (small) 6.dp else 8.dp, vertical = if (small) 2.dp else 4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title.uppercase(),
|
||||
color = ColorOnPrimaryLight,
|
||||
style = Typography.bodySmall.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontFamily = redHatDisplayFamily,
|
||||
fontSize = if (small) 8.sp else 12.sp
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PackageGridItem(
|
||||
item: PackageItem,
|
||||
navigateToDetailsScreen: (item: PackageItem) -> Unit,
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable { navigateToDetailsScreen(item) },
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
modifier = Modifier.padding(8.dp),
|
||||
) {
|
||||
Layout(
|
||||
content = {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data("TODO:")
|
||||
.crossfade(true)
|
||||
.error(R.drawable.default_game_icon)
|
||||
.build(),
|
||||
placeholder = ColorPainter(PlaceholderColor),
|
||||
contentDescription = "Package Icon",
|
||||
modifier = Modifier
|
||||
.aspectRatio(1f / 1f)
|
||||
.fillMaxWidth(),
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
Tag(title = item.dataType.name)
|
||||
}
|
||||
) { measurables, constraints ->
|
||||
val imagePlaceable = measurables[0].measure(constraints)
|
||||
val tagPlaceable = measurables.first { it.layoutId == "tag" }.measure(constraints)
|
||||
|
||||
layout(imagePlaceable.width, imagePlaceable.height) {
|
||||
imagePlaceable.placeRelative(0, 0)
|
||||
tagPlaceable.placeRelative(
|
||||
x = imagePlaceable.width - tagPlaceable.width - 16,
|
||||
y = 16,
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = item.name,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
style = Typography.bodyLarge,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 2,
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
ProvideTextStyle(value = Typography.bodySmall.copy(color = MaterialTheme.colorScheme.outline)) {
|
||||
Text(text = item.region)
|
||||
if (!item.minFW.isNullOrEmpty()) {
|
||||
Text(text = item.minFW)
|
||||
}
|
||||
if (item.pkgSize.isNotEmpty()) {
|
||||
Text(text = item.pkgSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
Row {
|
||||
if (item.pkgUrl.isNullOrEmpty()) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.no_url_icon),
|
||||
contentDescription = "No PKG URL",
|
||||
tint = Color(0xFFCB5A5A)
|
||||
)
|
||||
}
|
||||
if (item.zRif.isNullOrEmpty() && item.rap.isNullOrEmpty()) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.no_key_icon),
|
||||
contentDescription = "No License Key",
|
||||
tint = Color(0xFFCB5A5A)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LayoutDialog(
|
||||
onDismiss: () -> Unit,
|
||||
prefs: SettingsPreferences,
|
||||
viewModel: SettingsViewModel
|
||||
) {
|
||||
NPSAlertDialog(
|
||||
onDismiss = onDismiss,
|
||||
title = stringResource(id = R.string.layout),
|
||||
buttons = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(text = "Cancel")
|
||||
}
|
||||
}
|
||||
) {
|
||||
Column {
|
||||
NPSRadioButton(
|
||||
text = "List",
|
||||
selected = prefs.layout == ItemLayout.LIST.ordinal,
|
||||
onClick = { viewModel.setLayout(ItemLayout.LIST) },
|
||||
)
|
||||
NPSRadioButton(
|
||||
text = "Grid",
|
||||
selected = prefs.layout == ItemLayout.GRID.ordinal,
|
||||
onClick = { viewModel.setLayout(ItemLayout.GRID) },
|
||||
)
|
||||
}
|
||||
private fun hideUpButton() {
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.illusionware.npsbrowser.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
|
||||
class SplashActivity : BaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
if (hasFocus) hideSystemUI()
|
||||
}
|
||||
|
||||
private fun hideSystemUI() {
|
||||
// Enables regular immersive mode.
|
||||
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
|
||||
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||
// Set the content to appear under the system bars so that the
|
||||
// content doesn't resize when the system bars hide and show.
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
// Hide the nav bar and status bar
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
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,36 +0,0 @@
|
||||
package com.illusionware.npsbrowser.data
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
data class OnboardingPreferences(
|
||||
val seenOnboarding: Boolean,
|
||||
)
|
||||
|
||||
class OnboardingPreferencesRepository(
|
||||
private val dataStore: DataStore<Preferences>,
|
||||
) {
|
||||
companion object {
|
||||
private val SEEN_ONBOARDING = booleanPreferencesKey("seen_onboarding")
|
||||
}
|
||||
|
||||
fun getPreferences(): Flow<OnboardingPreferences> =
|
||||
runBlocking { dataStore.data.map { mapOnboardingSettings(it) } }
|
||||
|
||||
suspend fun setSeenOnboarding(seen: Boolean) {
|
||||
dataStore.edit { prefs ->
|
||||
prefs[SEEN_ONBOARDING] = seen
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapOnboardingSettings(prefs: Preferences): OnboardingPreferences {
|
||||
return OnboardingPreferences(
|
||||
prefs[SEEN_ONBOARDING] ?: false,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
package com.illusionware.npsbrowser.data
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import com.illusionware.npsbrowser.model.ConsoleType
|
||||
import com.illusionware.npsbrowser.model.PackageItemType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
val defaultDownloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + "/NPSBrowser"
|
||||
|
||||
enum class ItemLayout {
|
||||
LIST,
|
||||
GRID,
|
||||
}
|
||||
|
||||
enum class Theme {
|
||||
LIGHT,
|
||||
DARK,
|
||||
SYSTEM,
|
||||
}
|
||||
|
||||
data class SettingsPreferences(
|
||||
val appTheme: Int,
|
||||
val layout: Int,
|
||||
val psvGames: String,
|
||||
val ps3Games: String,
|
||||
val pspGames: String,
|
||||
val psxGames: String,
|
||||
val psmGames: String,
|
||||
val psvDlc: String,
|
||||
val ps3Dlc: String,
|
||||
val pspDlc: String,
|
||||
val psvThemes: String,
|
||||
val hmacKey: String,
|
||||
val downloadDir: String,
|
||||
val unpackDir: String,
|
||||
val unpackInDownload: Boolean,
|
||||
val deleteAfterUnpack: Boolean,
|
||||
val pkg2zipParams: String,
|
||||
val pkg2zipAutoDecrypt: Boolean,
|
||||
)
|
||||
|
||||
class SettingsPreferencesRepository(
|
||||
private val dataStore: DataStore<Preferences>
|
||||
) {
|
||||
companion object {
|
||||
private val APP_THEME = intPreferencesKey("app_theme")
|
||||
private val LAYOUT = intPreferencesKey("layout")
|
||||
private val PSV_GAMES = stringPreferencesKey("psv_games")
|
||||
private val PS3_GAMES = stringPreferencesKey("ps3_games")
|
||||
private val PSP_GAMES = stringPreferencesKey("psp_games")
|
||||
private val PSX_GAMES = stringPreferencesKey("psx_games")
|
||||
private val PSM_GAMES = stringPreferencesKey("psm_games")
|
||||
private val PSV_DLC = stringPreferencesKey("psv_dlc")
|
||||
private val PS3_DLC = stringPreferencesKey("ps3_dlc")
|
||||
private val PSP_DLC = stringPreferencesKey("psp_dlc")
|
||||
private val PSV_THEMES = stringPreferencesKey("psv_themes")
|
||||
private val HMAC_KEY = stringPreferencesKey("hmac_key")
|
||||
private val DOWNLOAD_DIR = stringPreferencesKey("download_dir")
|
||||
private val UNPACK_DIR = stringPreferencesKey("unpack_dir")
|
||||
private val UNPACK_IN_DOWNLOAD = booleanPreferencesKey("unpack_in_download")
|
||||
private val DELETE_AFTER_UNPACK = booleanPreferencesKey("delete_after_unpack")
|
||||
private val PKG2ZIP_PARAMS = stringPreferencesKey("pkg2zip_params")
|
||||
private val PKG2ZIP_AUTO_DECRYPT = booleanPreferencesKey("pkg2zip_auto_decrypt")
|
||||
}
|
||||
|
||||
private val tsvFiles = MutableSharedFlow<Triple<String, ConsoleType, PackageItemType>>()
|
||||
|
||||
fun getPreferences(): Flow<SettingsPreferences> =
|
||||
dataStore.data.map { mapSettingsPreferences(it) }
|
||||
|
||||
suspend fun availableTsvs(): MutableList<Triple<String, ConsoleType, PackageItemType>> {
|
||||
val list = mutableListOf<Triple<String, ConsoleType, PackageItemType>>()
|
||||
|
||||
return dataStore.data.map {
|
||||
if (it[PSV_GAMES]?.isNotEmpty() == true) list.add(Triple(it[PSV_GAMES] ?: "", ConsoleType.PSVITA, PackageItemType.GAME))
|
||||
if (it[PSV_DLC]?.isNotEmpty() == true) list.add(Triple(it[PSV_DLC] ?: "", ConsoleType.PSVITA, PackageItemType.DLC))
|
||||
if (it[PSV_THEMES]?.isNotEmpty() == true) list.add(Triple(it[PSV_THEMES] ?: "", ConsoleType.PSVITA, PackageItemType.THEME))
|
||||
if (it[PS3_GAMES]?.isNotEmpty() == true) list.add(Triple(it[PS3_GAMES] ?: "", ConsoleType.PS3, PackageItemType.GAME))
|
||||
if (it[PS3_DLC]?.isNotEmpty() == true) list.add(Triple(it[PS3_DLC] ?: "", ConsoleType.PS3, PackageItemType.DLC))
|
||||
if (it[PSP_GAMES]?.isNotEmpty() == true) list.add(Triple(it[PSP_GAMES] ?: "", ConsoleType.PSP, PackageItemType.GAME))
|
||||
if (it[PSP_DLC]?.isNotEmpty() == true) list.add(Triple(it[PSP_DLC] ?: "", ConsoleType.PSP, PackageItemType.DLC))
|
||||
if (it[PSX_GAMES]?.isNotEmpty() == true) list.add(Triple(it[PSX_GAMES] ?: "", ConsoleType.PSX, PackageItemType.GAME))
|
||||
if (it[PSM_GAMES]?.isNotEmpty() == true) list.add(Triple(it[PSM_GAMES] ?: "", ConsoleType.PSM, PackageItemType.GAME))
|
||||
|
||||
list
|
||||
}.first()
|
||||
}
|
||||
|
||||
suspend fun setTheme(theme: Theme) {
|
||||
dataStore.edit { prefs ->
|
||||
prefs[APP_THEME] = theme.ordinal
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setLayout(layout: ItemLayout) {
|
||||
dataStore.edit { prefs ->
|
||||
prefs[LAYOUT] = layout.ordinal
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setHMACKey(key: String) {
|
||||
dataStore.edit { prefs ->
|
||||
prefs[HMAC_KEY] = key
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setDownloadDir(dir: String) {
|
||||
dataStore.edit { prefs ->
|
||||
prefs[DOWNLOAD_DIR] = dir
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setUnpackDir(dir: String) {
|
||||
dataStore.edit { prefs ->
|
||||
prefs[UNPACK_DIR] = dir
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setUnpackInDownload(unpackInDownload: Boolean) {
|
||||
dataStore.edit { prefs ->
|
||||
prefs[UNPACK_IN_DOWNLOAD] = unpackInDownload
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setDeleteAfterUnpack(deleteAfterUnpack: Boolean) {
|
||||
dataStore.edit { prefs ->
|
||||
prefs[DELETE_AFTER_UNPACK] = deleteAfterUnpack
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setPkg2zipParams(params: String) {
|
||||
dataStore.edit { prefs ->
|
||||
prefs[PKG2ZIP_PARAMS] = params
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setPkg2zipAutoDecrypt(autoDecrypt: Boolean) {
|
||||
dataStore.edit { prefs ->
|
||||
prefs[PKG2ZIP_AUTO_DECRYPT] = autoDecrypt
|
||||
}
|
||||
}
|
||||
|
||||
fun tsvFiles() = tsvFiles
|
||||
|
||||
suspend fun setTSVFile(console: ConsoleType, contentType: PackageItemType, file: Uri) {
|
||||
tsvFiles.emit(Triple(file.toString(), console, contentType))
|
||||
|
||||
dataStore.edit { prefs ->
|
||||
when (console) {
|
||||
ConsoleType.PSVITA -> {
|
||||
when (contentType) {
|
||||
PackageItemType.GAME -> prefs[PSV_GAMES] = file.toString()
|
||||
PackageItemType.DLC -> prefs[PSV_DLC] = file.toString()
|
||||
PackageItemType.THEME -> prefs[PSV_THEMES] = file.toString()
|
||||
}
|
||||
}
|
||||
ConsoleType.PS3 -> {
|
||||
when (contentType) {
|
||||
PackageItemType.GAME -> prefs[PS3_GAMES] = file.toString()
|
||||
PackageItemType.DLC -> prefs[PS3_DLC] = file.toString()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
ConsoleType.PSP -> {
|
||||
when (contentType) {
|
||||
PackageItemType.GAME -> prefs[PSP_GAMES] = file.toString()
|
||||
PackageItemType.DLC -> prefs[PSP_DLC] = file.toString()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
ConsoleType.PSX -> {
|
||||
when (contentType) {
|
||||
PackageItemType.GAME -> prefs[PSX_GAMES] = file.toString()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
ConsoleType.PSM -> {
|
||||
when (contentType) {
|
||||
PackageItemType.GAME -> prefs[PSM_GAMES] = file.toString()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapSettingsPreferences(prefs: Preferences): SettingsPreferences {
|
||||
return SettingsPreferences(
|
||||
prefs[APP_THEME] ?: Theme.SYSTEM.ordinal,
|
||||
prefs[LAYOUT] ?: ItemLayout.LIST.ordinal,
|
||||
prefs[PSV_GAMES] ?: "",
|
||||
prefs[PS3_GAMES] ?: "",
|
||||
prefs[PSP_GAMES] ?: "",
|
||||
prefs[PSX_GAMES] ?: "",
|
||||
prefs[PSM_GAMES] ?: "",
|
||||
prefs[PSV_DLC] ?: "",
|
||||
prefs[PS3_DLC] ?: "",
|
||||
prefs[PSP_DLC] ?: "",
|
||||
prefs[PSV_THEMES] ?: "",
|
||||
prefs[HMAC_KEY] ?: "",
|
||||
prefs[DOWNLOAD_DIR] ?: defaultDownloadDir,
|
||||
prefs[UNPACK_DIR] ?: defaultDownloadDir,
|
||||
prefs[UNPACK_IN_DOWNLOAD] ?: false,
|
||||
prefs[DELETE_AFTER_UNPACK] ?: false,
|
||||
prefs[PKG2ZIP_PARAMS] ?: "-x {pkgFile} \"{zRifKey}\"",
|
||||
prefs[PKG2ZIP_AUTO_DECRYPT] ?: true,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
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
|
||||
if (app.version != null) {
|
||||
view?.findViewById<TextView>(R.id.singleAppVer)?.text = app.version
|
||||
} else {
|
||||
view?.findViewById<TextView>(R.id.singleAppVerTitle)?.visibility = View.INVISIBLE
|
||||
}
|
||||
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.singleAppCopyPKGButton)?.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.singleAppCopyPKGButton)?.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()
|
||||
}
|
||||
}
|
||||
|
||||
// click listener for the copy pkg button
|
||||
view?.findViewById<Button>(R.id.singleAppCopyPKGButton)?.setOnClickListener {
|
||||
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()
|
||||
}
|
||||
|
||||
// click listener for the copy zRIF 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
|
||||
}
|
||||
}
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
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 version = entry["Update Version"]
|
||||
val sha256 = entry["SHA256"]
|
||||
val minFW = entry["Required FW"]
|
||||
val app = AppData(
|
||||
id, region, name, link ?: "MISSING", license ?: "MISSING",
|
||||
contentID ?: "MISSING", lastDateTime ?: "MISSING",
|
||||
fileSize, version, 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
@@ -0,0 +1,7 @@
|
||||
package com.illusionware.npsbrowser.fragments.mainactivity
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class MainActivityViewModel : ViewModel() {
|
||||
// TODO: Implement the ViewModel
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.illusionware.npsbrowser.model
|
||||
|
||||
enum class ConsoleType {
|
||||
PSVITA,
|
||||
PS3,
|
||||
PSP,
|
||||
PSX,
|
||||
PSM,
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.illusionware.npsbrowser.model
|
||||
|
||||
data class PackageItem(
|
||||
val titleId: String,
|
||||
val region: String,
|
||||
val name: String,
|
||||
val pkgUrl: String?,
|
||||
val contentId: String?,
|
||||
val modificationDate: String?,
|
||||
val pkgSize: String,
|
||||
val sha256: String?,
|
||||
val zRif: String?,
|
||||
val rap: String?,
|
||||
val minFW: String?,
|
||||
|
||||
val consoleType: ConsoleType,
|
||||
val dataType: PackageItemType,
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.illusionware.npsbrowser.model
|
||||
|
||||
enum class PackageItemType {
|
||||
GAME,
|
||||
DLC,
|
||||
THEME,
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
package com.illusionware.npsbrowser.ui.components
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Eco
|
||||
import androidx.compose.material3.FloatingActionButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.disabled
|
||||
import androidx.compose.ui.semantics.onClick
|
||||
import androidx.compose.ui.semantics.onLongClick
|
||||
import androidx.compose.ui.semantics.role
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun NPSFloatingActionButton(
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
shape: Shape = FloatingActionButtonDefaults.shape,
|
||||
containerColor: Color = FloatingActionButtonDefaults.containerColor,
|
||||
contentColor: Color = MaterialTheme.colorScheme.primary,
|
||||
elevation: Dp = 6.dp,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.alpha(if (enabled) 1f else 0.5f)
|
||||
.then(if (enabled) Modifier.shadow(6.dp, shape) else Modifier.clip(shape))
|
||||
.then(if (enabled) Modifier.combinedClickable(
|
||||
onClick = { onClick() },
|
||||
onLongClick = {
|
||||
if (onLongClick != null) {
|
||||
onLongClick()
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
}
|
||||
) else Modifier)
|
||||
.semantics(
|
||||
mergeDescendants = true,
|
||||
properties = {
|
||||
if (!enabled) disabled()
|
||||
onClick { onClick(); true }
|
||||
if (onLongClick != null) {
|
||||
onLongClick { onLongClick(); true }
|
||||
}
|
||||
role = Role.Button
|
||||
},
|
||||
),
|
||||
color = containerColor,
|
||||
tonalElevation = 3.dp,
|
||||
shadowElevation = elevation,
|
||||
contentColor = contentColor,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Row {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun NPSFABPreview() {
|
||||
NPSFloatingActionButton(
|
||||
onClick = {},
|
||||
) {
|
||||
Icon(imageVector = Icons.Outlined.Eco, contentDescription = "Eco")
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,891 +0,0 @@
|
||||
package com.illusionware.npsbrowser.ui.screens
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
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.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
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.ColumnScope
|
||||
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.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
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.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.ContentPaste
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.illusionware.npsbrowser.data.SettingsPreferences
|
||||
import com.illusionware.npsbrowser.model.ConsoleType
|
||||
import com.illusionware.npsbrowser.model.PackageItemType
|
||||
import com.illusionware.npsbrowser.ui.components.NPSIconButton
|
||||
import com.illusionware.npsbrowser.viewmodels.OnboardingViewModel
|
||||
import com.illusionware.npsbrowser.viewmodels.SettingsViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
const val PAGE_COUNT = 3
|
||||
|
||||
val readexProFamily = FontFamily(
|
||||
Font(R.font.readex_pro_extra_light, FontWeight.ExtraLight),
|
||||
Font(R.font.readex_pro_light, FontWeight.Light),
|
||||
Font(R.font.readex_pro_regular, FontWeight.Normal),
|
||||
Font(R.font.readex_pro_medium, FontWeight.Medium),
|
||||
Font(R.font.readex_pro_semi_bold, FontWeight.SemiBold),
|
||||
Font(R.font.readex_pro_bold, FontWeight.Bold),
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
@Composable
|
||||
fun OnBoarding(
|
||||
navigateToHome: () -> Unit = {},
|
||||
darkTheme: Boolean = false,
|
||||
onboardingViewModel: OnboardingViewModel = viewModel(
|
||||
factory = OnboardingViewModel.Factory
|
||||
),
|
||||
settingsViewModel: SettingsViewModel = viewModel(
|
||||
factory = SettingsViewModel.Factory
|
||||
),
|
||||
) {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = 0,
|
||||
initialPageOffsetFraction = 0f
|
||||
)
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val settingsPrefs = settingsViewModel.uiState.collectAsStateWithLifecycle().value
|
||||
|
||||
val darkIcons = when (pagerState.currentPage) {
|
||||
0 -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
DisposableEffect(systemUiController) {
|
||||
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) }
|
||||
},
|
||||
settingsViewModel,
|
||||
)
|
||||
// 2 -> OnBoardingStoragePermission(
|
||||
// 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) }
|
||||
// },
|
||||
// )
|
||||
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,
|
||||
settingsViewModel,
|
||||
onboardingViewModel,
|
||||
settingsPrefs,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
fun OnBoardingSetupTSVs(
|
||||
page: Int = 1,
|
||||
incrementPage: () -> Unit = {},
|
||||
skipToEnd: () -> Unit = {},
|
||||
settingsViewModel: SettingsViewModel,
|
||||
) {
|
||||
Scaffold { padding ->
|
||||
Column(
|
||||
Modifier
|
||||
.padding(padding)
|
||||
.padding(vertical = 32.dp)
|
||||
.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(text = "Set up 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),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
TSVGroup(title = "PS Vita") {
|
||||
TSVButton(
|
||||
"Games",
|
||||
onFinish = { settingsViewModel.setTSVFile(ConsoleType.PSVITA, PackageItemType.GAME, it) }
|
||||
)
|
||||
TSVButton(
|
||||
"Themes",
|
||||
onFinish = { settingsViewModel.setTSVFile(ConsoleType.PSVITA, PackageItemType.THEME, it) }
|
||||
)
|
||||
TSVButton(
|
||||
"DLC",
|
||||
onFinish = { settingsViewModel.setTSVFile(ConsoleType.PSVITA, PackageItemType.DLC, it) }
|
||||
)
|
||||
}
|
||||
TSVGroup(title = "PSP") {
|
||||
TSVButton(
|
||||
"Games",
|
||||
onFinish = { settingsViewModel.setTSVFile(ConsoleType.PSP, PackageItemType.GAME, it) }
|
||||
)
|
||||
TSVButton(
|
||||
"DLC",
|
||||
onFinish = { settingsViewModel.setTSVFile(ConsoleType.PSP, PackageItemType.DLC, it) }
|
||||
)
|
||||
Box(modifier = Modifier.width(110.dp))
|
||||
}
|
||||
TSVGroup(title = "PS3") {
|
||||
TSVButton(
|
||||
"Games",
|
||||
onFinish = { settingsViewModel.setTSVFile(ConsoleType.PS3, PackageItemType.GAME, it) }
|
||||
)
|
||||
TSVButton(
|
||||
"DLC",
|
||||
onFinish = { settingsViewModel.setTSVFile(ConsoleType.PS3, PackageItemType.DLC, it) }
|
||||
)
|
||||
Box(modifier = Modifier.width(110.dp))
|
||||
}
|
||||
TSVGroup(title = "PSX") {
|
||||
TSVButton(
|
||||
"Games",
|
||||
onFinish = { settingsViewModel.setTSVFile(ConsoleType.PSX, PackageItemType.GAME, it) }
|
||||
)
|
||||
}
|
||||
TSVGroup(title = "PSM") {
|
||||
TSVButton(
|
||||
"Games",
|
||||
onFinish = { settingsViewModel.setTSVFile(ConsoleType.PSM, PackageItemType.GAME, it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
) {
|
||||
OnBoardingBottomButtons(
|
||||
page = page,
|
||||
incrementPage = incrementPage,
|
||||
skipToEnd = skipToEnd,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ColumnGroup(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
titleModifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Text(
|
||||
modifier = titleModifier,
|
||||
text = title,
|
||||
style = TextStyle.Default.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp,
|
||||
),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@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 TSVButton(title: String, onFinish: (Uri) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
var hasSelected by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val filePicker = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocument(),
|
||||
) { fileUri ->
|
||||
if (fileUri != null) {
|
||||
context.contentResolver.takePersistableUriPermission(
|
||||
fileUri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
onFinish(fileUri)
|
||||
hasSelected = true
|
||||
}
|
||||
}
|
||||
FilledTonalButton(
|
||||
onClick = {
|
||||
filePicker.launch(arrayOf("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 OnBoardingStoragePermission(
|
||||
// page: Int = 2,
|
||||
// incrementPage: () -> Unit = {},
|
||||
// decrementPage: () -> Unit = {},
|
||||
// skipToEnd: () -> Unit = {},
|
||||
//) {
|
||||
// val hasPermission = when (PackageManager.PERMISSION_GRANTED) {
|
||||
// ContextCompat.checkSelfPermission(
|
||||
// LocalContext.current,
|
||||
// Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
// ) -> true
|
||||
// else -> false
|
||||
// }
|
||||
//
|
||||
// val permissionLegacy = rememberLauncherForActivityResult(
|
||||
// contract = ActivityResultContracts.RequestPermission(),
|
||||
// ) {}
|
||||
//
|
||||
// Scaffold { padding ->
|
||||
// Column(
|
||||
// Modifier
|
||||
// .padding(padding)
|
||||
// .padding(16.dp)
|
||||
// .fillMaxSize(),
|
||||
// verticalArrangement = Arrangement.SpaceBetween
|
||||
// ) {
|
||||
// Column(
|
||||
// Modifier
|
||||
// .padding(vertical = 32.dp)
|
||||
// .weight(1.0f)
|
||||
// .fillMaxHeight(),
|
||||
// horizontalAlignment = Alignment.CenterHorizontally,
|
||||
// ) {
|
||||
// Image(
|
||||
// painter = painterResource(id = R.drawable.folder_bubbles),
|
||||
// contentDescription = "Folder Image",
|
||||
// contentScale = ContentScale.Crop,
|
||||
// modifier = Modifier.fillMaxWidth()
|
||||
// )
|
||||
// Text(
|
||||
// text = "Storage".uppercase(),
|
||||
// style = TextStyle.Default.copy(
|
||||
// fontWeight = FontWeight.Bold,
|
||||
// fontSize = 28.sp,
|
||||
// fontFamily = readexProFamily
|
||||
// ),
|
||||
// )
|
||||
// Spacer(modifier = Modifier.height(24.dp))
|
||||
// Text(
|
||||
// "Granting storage permission allows us to bypass newer Android limitations and choose any directory as your download directory",
|
||||
// style = TextStyle.Default.copy(
|
||||
// fontSize = 20.sp,
|
||||
// fontFamily = readexProFamily
|
||||
// ),
|
||||
// textAlign = TextAlign.Center,
|
||||
// )
|
||||
// Spacer(modifier = Modifier
|
||||
// .fillMaxHeight()
|
||||
// .weight(1.0f))
|
||||
// Button(
|
||||
// onClick = {
|
||||
// permissionLegacy.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
// },
|
||||
// modifier = Modifier.fillMaxWidth(),
|
||||
// enabled = !hasPermission,
|
||||
// ) {
|
||||
// Text(
|
||||
// text = if (hasPermission) "Granted" else "Grant",
|
||||
// style = TextStyle.Default.copy(
|
||||
// fontSize = 18.sp,
|
||||
// fontFamily = readexProFamily
|
||||
// ),
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// Column(Modifier.fillMaxWidth()) {
|
||||
// OnBoardingBottomButtons(
|
||||
// page = page,
|
||||
// incrementPage = incrementPage,
|
||||
// decrementPage = decrementPage,
|
||||
// skipToEnd = skipToEnd
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
@Composable
|
||||
fun OnBoardingSetupSettings(
|
||||
page: Int = 2,
|
||||
incrementPage: () -> Unit = {},
|
||||
decrementPage: () -> Unit = {},
|
||||
skipToEnd: () -> Unit = {},
|
||||
navigateToHome: () -> Unit = {},
|
||||
settingsViewModel: SettingsViewModel,
|
||||
onboardingViewModel: OnboardingViewModel,
|
||||
settingsPrefs: SettingsPreferences,
|
||||
) {
|
||||
val clipboard = LocalClipboardManager.current
|
||||
|
||||
var hmacKey by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
|
||||
|
||||
val downloadDirPicker = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocumentTree(),
|
||||
) { dirUri ->
|
||||
if (dirUri != null) {
|
||||
settingsViewModel.setDownloadDir(dirUri.toString())
|
||||
}
|
||||
}
|
||||
|
||||
val unpackDirPicker = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocumentTree(),
|
||||
) { dirUri ->
|
||||
if (dirUri != null) {
|
||||
settingsViewModel.setUnpackDir(dirUri.toString())
|
||||
}
|
||||
}
|
||||
|
||||
fun setHmacKey(key: String) {
|
||||
if (key.isNotEmpty()) {
|
||||
settingsViewModel.setHMACKey(key)
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold { padding ->
|
||||
Column(
|
||||
Modifier
|
||||
.padding(padding)
|
||||
.padding(vertical = 32.dp)
|
||||
.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(text = "Configure App", style = Typography.displayMedium)
|
||||
Text(
|
||||
text = "Set the app's settings to your liking and control how it behaves",
|
||||
style = Typography.titleMedium.copy(color = MaterialTheme.colorScheme.outline),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.weight(1.0f, fill = false)
|
||||
.padding(vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
ColumnGroup(title = "HMAC Key", modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
Text(
|
||||
text = "The HMAC Key is used to decrypt the PS Vita game updates' urls. We cannot provide this key since it's the property of Sony",
|
||||
style = Typography.bodyMedium.copy(color = MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = hmacKey,
|
||||
onValueChange = {
|
||||
hmacKey = it
|
||||
setHmacKey(it.text)
|
||||
},
|
||||
label = { Text(text = "HMAC Key") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
trailingIcon = {
|
||||
NPSIconButton(
|
||||
tooltip = "Paste",
|
||||
onClick = {
|
||||
val text = clipboard.getText()?.text ?: ""
|
||||
hmacKey = TextFieldValue(text, selection = TextRange(text.length))
|
||||
setHmacKey(text)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ContentPaste,
|
||||
contentDescription = "Paste",
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
ColumnGroup(title = "Downloads", titleModifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
text = "Choose your desired directory to download and unpack packages",
|
||||
style = Typography.bodyMedium.copy(color = MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
Column {
|
||||
Toggleable(
|
||||
title = "Unpack in download directory",
|
||||
checked = settingsPrefs.unpackInDownload,
|
||||
onCheckedChange = {
|
||||
settingsViewModel.setUnpackInDownload(it)
|
||||
},
|
||||
)
|
||||
Toggleable(
|
||||
title = "Delete package after unpacking",
|
||||
checked = settingsPrefs.deleteAfterUnpack,
|
||||
onCheckedChange = {
|
||||
settingsViewModel.setDeleteAfterUnpack(it)
|
||||
},
|
||||
)
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { downloadDirPicker.launch(Uri.EMPTY) }
|
||||
) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = "Download Directory",
|
||||
)
|
||||
Text(
|
||||
text = Uri.decode(settingsPrefs.downloadDir).ifEmpty { "None" },
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { unpackDirPicker.launch(Uri.EMPTY) }
|
||||
) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = "Unpack Directory",
|
||||
)
|
||||
Text(
|
||||
text = Uri.decode(settingsPrefs.unpackDir).ifEmpty { "None" },
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ColumnGroup(title = "PKG2ZIP", titleModifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
text = "Modify the parameters and arguments of pkg2zip",
|
||||
style = Typography.bodyMedium.copy(color = MaterialTheme.colorScheme.outline),
|
||||
)
|
||||
Toggleable(
|
||||
title = "Automatically decrypt downloaded content",
|
||||
checked = settingsPrefs.pkg2zipAutoDecrypt,
|
||||
onCheckedChange = {
|
||||
settingsViewModel.setPkg2zipAutoDecrypt(it)
|
||||
},
|
||||
)
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
value = settingsPrefs.pkg2zipParams,
|
||||
onValueChange = {
|
||||
settingsViewModel.setPkg2zipParams(it)
|
||||
}, // TODO: validate args are legal ??
|
||||
label = { Text(text = "Pkg2zip args") },
|
||||
placeholder = { Text(text = "-x {pkgFile} \"{zRifKey}\"") },
|
||||
)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
) {
|
||||
OnBoardingBottomButtons(
|
||||
page = page,
|
||||
incrementPage = incrementPage,
|
||||
decrementPage = decrementPage,
|
||||
skipToEnd = skipToEnd,
|
||||
navigateToHome = navigateToHome,
|
||||
settingsViewModel = settingsViewModel,
|
||||
onboardingViewModel = onboardingViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Toggleable(title: String, checked: Boolean, onCheckedChange: (Boolean) -> Unit) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clickable onCheckedChange@{
|
||||
onCheckedChange(!checked)
|
||||
}
|
||||
.padding(vertical = 16.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(text = title, color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.weight(1.0f))
|
||||
Switch(checked = checked, onCheckedChange = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OnBoardingBottomButtons(
|
||||
page: Int,
|
||||
incrementPage: () -> Unit = {},
|
||||
decrementPage: () -> Unit = {},
|
||||
skipToEnd: () -> Unit = {},
|
||||
navigateToHome: () -> Unit = {},
|
||||
settingsViewModel: SettingsViewModel? = null,
|
||||
onboardingViewModel: OnboardingViewModel? = null
|
||||
) {
|
||||
val finalPage = PAGE_COUNT - 1
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(0.1f),
|
||||
horizontalArrangement = if (page != finalPage) Arrangement.SpaceBetween else Arrangement.End,
|
||||
) {
|
||||
if (page != finalPage) {
|
||||
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 == finalPage) {
|
||||
Button(
|
||||
onClick = {
|
||||
navigateToHome()
|
||||
onboardingViewModel?.finishOnboarding(settingsViewModel!!.shouldCreateDefaultDir())
|
||||
},
|
||||
contentPadding = PaddingValues(horizontal = 18.dp)
|
||||
) {
|
||||
Text(text = "Finish")
|
||||
}
|
||||
} else {
|
||||
Button(
|
||||
onClick = incrementPage,
|
||||
contentPadding = PaddingValues(horizontal = 18.dp)
|
||||
) {
|
||||
Text(text = "Next")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,456 +0,0 @@
|
||||
package com.illusionware.npsbrowser.ui.screens
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
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.WindowInsets
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.DownloadDone
|
||||
import androidx.compose.material.icons.filled.Public
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SheetState
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.ColorPainter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.illusionware.npsbrowser.R
|
||||
import com.illusionware.npsbrowser.activities.PlaceholderColor
|
||||
import com.illusionware.npsbrowser.data.ItemLayout
|
||||
import com.illusionware.npsbrowser.data.SettingsPreferences
|
||||
import com.illusionware.npsbrowser.data.Theme
|
||||
import com.illusionware.npsbrowser.ui.components.NPSAlertDialog
|
||||
import com.illusionware.npsbrowser.ui.components.NPSFloatingActionButton
|
||||
import com.illusionware.npsbrowser.ui.components.NPSIconButton
|
||||
import com.illusionware.npsbrowser.ui.theme.ColorSecondaryLight
|
||||
import com.illusionware.npsbrowser.ui.theme.Typography
|
||||
import com.illusionware.npsbrowser.viewmodels.PackageDetailsViewModel
|
||||
import com.illusionware.npsbrowser.viewmodels.SettingsViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PackageDetails(
|
||||
navigationGoBack: () -> Unit = {},
|
||||
settingsViewModel: SettingsViewModel = viewModel(
|
||||
factory = SettingsViewModel.Factory
|
||||
),
|
||||
packageDetailsViewModel: PackageDetailsViewModel,
|
||||
) {
|
||||
val clipboard = LocalClipboardManager.current
|
||||
val sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true
|
||||
)
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val scope = rememberCoroutineScope()
|
||||
val prefs = settingsViewModel.uiState.collectAsStateWithLifecycle().value
|
||||
|
||||
val item = packageDetailsViewModel.uiState.collectAsStateWithLifecycle().value.item!!
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(snackbarHostState)},
|
||||
topBar = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.padding(vertical = 8.dp, horizontal = 4.dp),
|
||||
) {
|
||||
NPSIconButton(tooltip = "Go Back", onClick = navigationGoBack) {
|
||||
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "Go Back" )
|
||||
}
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
NPSFloatingActionButton(
|
||||
onClick = {
|
||||
if (!item.zRif.isNullOrEmpty()) {
|
||||
clipboard.setText(AnnotatedString(item.zRif))
|
||||
} else if (!item.rap.isNullOrEmpty()) {
|
||||
clipboard.setText(AnnotatedString(item.rap))
|
||||
}
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar("License key copied to clipboard")
|
||||
}
|
||||
},
|
||||
enabled = !item.zRif.isNullOrEmpty() || !item.rap.isNullOrEmpty()
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.key_copy_fill),
|
||||
contentDescription = "Copy License Key"
|
||||
)
|
||||
}
|
||||
NPSFloatingActionButton(
|
||||
onClick = { /* TODO: */ },
|
||||
onLongClick = { showBottomSheet = true },
|
||||
enabled = !item.pkgUrl.isNullOrEmpty()
|
||||
) {
|
||||
Icon(imageVector = Icons.Filled.Download, contentDescription = "Download")
|
||||
}
|
||||
}
|
||||
},
|
||||
) { padding ->
|
||||
Column(Modifier.padding(padding)) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data("TODO")
|
||||
.crossfade(true)
|
||||
.error(R.drawable.default_game_icon)
|
||||
.build(),
|
||||
placeholder = ColorPainter(PlaceholderColor),
|
||||
contentDescription = "Package Icon",
|
||||
modifier = Modifier
|
||||
.aspectRatio(1f / 1f)
|
||||
.fillMaxWidth(),
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = "[${item.titleId}] ${item.name}",
|
||||
style = Typography.headlineSmall.copy(fontSize = 20.sp),
|
||||
fontWeight = FontWeight.Light,
|
||||
)
|
||||
if (!item.contentId.isNullOrEmpty()) {
|
||||
Spacer(modifier = Modifier.padding(4.dp))
|
||||
Text(
|
||||
text = item.contentId,
|
||||
style = Typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.padding(8.dp))
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
if (item.region.isNotEmpty()) {
|
||||
Info(
|
||||
label = item.region,
|
||||
iconDescription = "Region",
|
||||
icon = Icons.Filled.Public
|
||||
)
|
||||
}
|
||||
if (item.pkgSize.isNotEmpty()) {
|
||||
Info(
|
||||
label = item.pkgSize,
|
||||
iconDescription = "Package",
|
||||
icon = painterResource(id = R.drawable.package_ic),
|
||||
)
|
||||
}
|
||||
if (!item.minFW.isNullOrEmpty()) {
|
||||
Info(
|
||||
label = item.minFW,
|
||||
iconDescription = "Minimum Firmware",
|
||||
icon = painterResource(id = R.drawable.gear),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showBottomSheet) {
|
||||
OneTimeConfigBottomSheet(
|
||||
onDismiss = {
|
||||
scope.launch {
|
||||
sheetState.hide()
|
||||
showBottomSheet = false
|
||||
}
|
||||
},
|
||||
sheetState = sheetState,
|
||||
prefs = prefs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun OneTimeConfigBottomSheet(
|
||||
sheetState: SheetState,
|
||||
onDismiss: () -> Unit,
|
||||
prefs: SettingsPreferences
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = sheetState,
|
||||
windowInsets = WindowInsets(top = 0.dp),
|
||||
) {
|
||||
OneTimeConfigSheetContent(prefs)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Info(label: String, iconDescription: String, icon: Painter) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = icon,
|
||||
contentDescription = iconDescription,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(28.dp)
|
||||
)
|
||||
Text(
|
||||
text = label,
|
||||
style = Typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Info(label: String, iconDescription: String, icon: ImageVector) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = iconDescription,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(28.dp)
|
||||
)
|
||||
Text(
|
||||
text = label,
|
||||
style = Typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Option(
|
||||
title: String,
|
||||
value: String,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = 16.dp, horizontal = 24.dp)
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(text = title, color = MaterialTheme.colorScheme.onBackground)
|
||||
Text(text = value, color = ColorSecondaryLight, fontSize = 14.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun OneTimeConfigSheetContent(prefs: SettingsPreferences) {
|
||||
var autoDecrypt by remember { mutableStateOf(prefs.pkg2zipAutoDecrypt) }
|
||||
var unpackInPlace by remember { mutableStateOf(prefs.unpackInDownload) }
|
||||
var deleteAfter by remember { mutableStateOf(prefs.deleteAfterUnpack) }
|
||||
var downloadDir by remember { mutableStateOf(Uri.decode(prefs.downloadDir)) }
|
||||
var decParamsDialog by remember { mutableStateOf(false) }
|
||||
var unpackDir by remember { mutableStateOf(Uri.decode(prefs.unpackDir)) }
|
||||
var pkg2zipParams by remember { mutableStateOf(prefs.pkg2zipParams) }
|
||||
var displayPkg2zipParams by remember { mutableStateOf(prefs.pkg2zipParams) }
|
||||
|
||||
val downloadDirPicker = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocumentTree(),
|
||||
) { dirUri ->
|
||||
if (dirUri != null) {
|
||||
downloadDir = dirUri.toString()
|
||||
}
|
||||
}
|
||||
|
||||
val unpackDirPicker = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocumentTree(),
|
||||
) { dirUri ->
|
||||
if (dirUri != null) {
|
||||
unpackDir = dirUri.toString()
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.navigationBarsPadding()
|
||||
) {
|
||||
Text(
|
||||
text = "One-time Configuration",
|
||||
textAlign = TextAlign.Center,
|
||||
style = Typography.titleLarge
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp)
|
||||
) {
|
||||
FilterChip(
|
||||
label = { Text(text = "Decrypt automatically") },
|
||||
selected = autoDecrypt,
|
||||
onClick = { autoDecrypt = !autoDecrypt }
|
||||
)
|
||||
FilterChip(
|
||||
label = { Text(text = "Unpack in place") },
|
||||
selected = unpackInPlace,
|
||||
onClick = { unpackInPlace = !unpackInPlace },
|
||||
)
|
||||
FilterChip(
|
||||
label = { Text(text = "Delete after") },
|
||||
selected = deleteAfter,
|
||||
onClick = { deleteAfter = !deleteAfter },
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
horizontalAlignment = Alignment.End,
|
||||
) {
|
||||
Column {
|
||||
Option(
|
||||
title = "Download Directory",
|
||||
value = Uri.decode(downloadDir).ifEmpty { "None" },
|
||||
onClick = {
|
||||
downloadDirPicker.launch(Uri.EMPTY)
|
||||
}
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = !unpackInPlace
|
||||
) {
|
||||
Option(
|
||||
title = "Unpack Directory",
|
||||
value = Uri.decode(unpackDir).ifEmpty { "None" },
|
||||
onClick = {
|
||||
unpackDirPicker.launch(Uri.EMPTY)
|
||||
}
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = autoDecrypt
|
||||
) {
|
||||
Option(
|
||||
title = "Decryption Parameters",
|
||||
value = pkg2zipParams,
|
||||
onClick = { decParamsDialog = true }
|
||||
)
|
||||
}
|
||||
}
|
||||
Button(
|
||||
onClick = { /*TODO: do the download*/ },
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.DownloadDone,
|
||||
contentDescription = "Download",
|
||||
Modifier.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
Text("Download")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (decParamsDialog) {
|
||||
NPSAlertDialog(
|
||||
onDismiss = {
|
||||
decParamsDialog = false
|
||||
displayPkg2zipParams = pkg2zipParams
|
||||
},
|
||||
title = "Decryption Parameters",
|
||||
buttons = {
|
||||
TextButton(onClick = {
|
||||
pkg2zipParams = displayPkg2zipParams
|
||||
decParamsDialog = false
|
||||
}) {
|
||||
Text(text = "OK")
|
||||
}
|
||||
}
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = displayPkg2zipParams,
|
||||
label = { Text(text = "Decryption Parameters") },
|
||||
onValueChange = {
|
||||
displayPkg2zipParams = it
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF)
|
||||
fun BottomSheetPreview() {
|
||||
val prefs = SettingsPreferences(
|
||||
Theme.SYSTEM.ordinal,
|
||||
ItemLayout.LIST.ordinal,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
"",
|
||||
true,
|
||||
)
|
||||
|
||||
Column {
|
||||
OneTimeConfigSheetContent(prefs = prefs)
|
||||
}
|
||||
}
|
||||
@@ -1,581 +0,0 @@
|
||||
package com.illusionware.npsbrowser.ui.screens
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
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.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
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.filled.ContentPaste
|
||||
import androidx.compose.material.icons.outlined.ArrowDropDown
|
||||
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.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
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.SideEffect
|
||||
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.draw.alpha
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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 com.illusionware.npsbrowser.viewmodels.SettingsViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.illusionware.npsbrowser.data.SettingsPreferences
|
||||
import com.illusionware.npsbrowser.data.Theme
|
||||
import com.illusionware.npsbrowser.model.ConsoleType
|
||||
import com.illusionware.npsbrowser.model.PackageItemType
|
||||
|
||||
private val Themes = hashMapOf(
|
||||
Theme.LIGHT.ordinal to "Light",
|
||||
Theme.DARK.ordinal to "Dark",
|
||||
Theme.SYSTEM.ordinal to "Use System Theme"
|
||||
)
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun SettingsScreen(
|
||||
navigationGoBack: () -> Unit = {},
|
||||
viewModel: SettingsViewModel = viewModel(
|
||||
factory = SettingsViewModel.Factory
|
||||
)
|
||||
) {
|
||||
val prefs = viewModel.uiState.collectAsStateWithLifecycle().value
|
||||
|
||||
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)
|
||||
}
|
||||
},
|
||||
contentWindowInsets = WindowInsets(bottom = 0.dp),
|
||||
) { padding ->
|
||||
Column(modifier = Modifier
|
||||
.padding(padding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(20.dp),
|
||||
modifier = Modifier
|
||||
.padding(vertical = 16.dp)
|
||||
.navigationBarsPadding()
|
||||
) {
|
||||
Appearance(viewModel, prefs)
|
||||
TsvFiles(viewModel, prefs)
|
||||
Updates(viewModel, prefs)
|
||||
Downloads(viewModel, prefs)
|
||||
Pkg2Zip(viewModel, prefs)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Appearance(viewModel: SettingsViewModel, prefs: SettingsPreferences) {
|
||||
var themeOpenDialog by remember { mutableStateOf(false) }
|
||||
|
||||
SettingGroup(title = stringResource(id = R.string.appearance)) {
|
||||
DialogSetting(
|
||||
title = stringResource(id = R.string.theme),
|
||||
value = Themes[prefs.appTheme] ?: "None",
|
||||
icon = Icons.Outlined.Palette,
|
||||
onClick = { themeOpenDialog = true }
|
||||
)
|
||||
}
|
||||
if (themeOpenDialog) {
|
||||
ThemeDialog(
|
||||
prefs = prefs,
|
||||
onDismiss = { themeOpenDialog = false },
|
||||
onThemeChange = {
|
||||
viewModel.setTheme(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TsvFiles(viewModel: SettingsViewModel, prefs: SettingsPreferences) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
SettingGroup(title = "TSV Files") {
|
||||
TSVPicker(
|
||||
title = "PSV Games",
|
||||
value = Uri.decode(prefs.psvGames.ifEmpty { "Browse" }),
|
||||
icon = painterResource(id = R.drawable.vita_icon),
|
||||
onFilePick = {
|
||||
viewModel.setTSVFile(ConsoleType.PSVITA, PackageItemType.GAME, it)
|
||||
}
|
||||
)
|
||||
TSVPicker(
|
||||
title = "PSV DLC",
|
||||
value = Uri.decode(prefs.psvDlc.ifEmpty { "Browse" }),
|
||||
icon = null,
|
||||
onFilePick = {
|
||||
viewModel.setTSVFile(ConsoleType.PSVITA, PackageItemType.DLC, it)
|
||||
}
|
||||
)
|
||||
TSVPicker(
|
||||
title = "PSV Themes",
|
||||
value = Uri.decode(prefs.psvThemes.ifEmpty { "Browse" }),
|
||||
icon = null,
|
||||
onFilePick = {
|
||||
viewModel.setTSVFile(ConsoleType.PSVITA, PackageItemType.THEME, it)
|
||||
},
|
||||
)
|
||||
TSVPicker(
|
||||
title = "PS3 Games",
|
||||
value = Uri.decode(prefs.ps3Games).ifEmpty { "Browse" },
|
||||
icon = painterResource(id = R.drawable.ps3_icon),
|
||||
onFilePick = {
|
||||
viewModel.setTSVFile(ConsoleType.PS3, PackageItemType.GAME, it)
|
||||
}
|
||||
)
|
||||
if (!expanded) {
|
||||
DialogSetting(
|
||||
title = "Expand",
|
||||
value = "PS3 DLC, PSP Games, PSP DLC, PSX Games, PSM Games",
|
||||
icon = Icons.Outlined.ArrowDropDown,
|
||||
singleLine = true,
|
||||
onClick = { expanded = true },
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = expanded,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
TSVPicker(
|
||||
title = "PS3 DLC",
|
||||
value = Uri.decode(prefs.ps3Dlc).ifEmpty { "Browse" },
|
||||
icon = null,
|
||||
onFilePick = {
|
||||
viewModel.setTSVFile(ConsoleType.PS3, PackageItemType.DLC, it)
|
||||
}
|
||||
)
|
||||
TSVPicker(
|
||||
title = "PSP Games",
|
||||
value = Uri.decode(prefs.pspGames).ifEmpty { "Browse" },
|
||||
icon = painterResource(id = R.drawable.psp_icon),
|
||||
onFilePick = {
|
||||
viewModel.setTSVFile(ConsoleType.PSP, PackageItemType.GAME, it)
|
||||
}
|
||||
)
|
||||
TSVPicker(
|
||||
title = "PSP DLC",
|
||||
value = Uri.decode(prefs.pspDlc).ifEmpty { "Browse" },
|
||||
icon = null,
|
||||
onFilePick = {
|
||||
viewModel.setTSVFile(ConsoleType.PSP, PackageItemType.DLC, it)
|
||||
}
|
||||
)
|
||||
TSVPicker(
|
||||
title = "PSX Games",
|
||||
value = Uri.decode(prefs.psxGames).ifEmpty { "Browse" },
|
||||
icon = painterResource(id = R.drawable.psx_icon),
|
||||
onFilePick = {
|
||||
viewModel.setTSVFile(ConsoleType.PSX, PackageItemType.GAME, it)
|
||||
}
|
||||
)
|
||||
TSVPicker(
|
||||
title = "PSM Games",
|
||||
value = Uri.decode(prefs.psmGames).ifEmpty { "Browse" },
|
||||
icon = painterResource(id = R.drawable.psm_icon),
|
||||
onFilePick = {
|
||||
viewModel.setTSVFile(ConsoleType.PSM, PackageItemType.GAME, it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TSVPicker(
|
||||
title: String,
|
||||
value: String,
|
||||
icon: Painter? = null,
|
||||
onFilePick: (Uri) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val filePicker = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocument(),
|
||||
) { fileUri ->
|
||||
if (fileUri != null) {
|
||||
context.contentResolver.takePersistableUriPermission(
|
||||
fileUri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
onFilePick(fileUri)
|
||||
}
|
||||
}
|
||||
|
||||
DialogSetting(
|
||||
title = title,
|
||||
value = value,
|
||||
icon = icon,
|
||||
onClick = {
|
||||
filePicker.launch(arrayOf("text/tab-separated-values"))
|
||||
}
|
||||
)}
|
||||
|
||||
@Composable
|
||||
fun Updates(viewModel: SettingsViewModel, prefs: SettingsPreferences) {
|
||||
val clipboard = LocalClipboardManager.current
|
||||
|
||||
var hmacOpenDialog by remember { mutableStateOf(false) }
|
||||
var dialogHmacKey by remember { mutableStateOf("") }
|
||||
|
||||
SideEffect {
|
||||
dialogHmacKey = prefs.hmacKey
|
||||
}
|
||||
|
||||
SettingGroup(title = "Updates") {
|
||||
DialogSetting(
|
||||
title = "HMAC Key",
|
||||
value = prefs.hmacKey.ifEmpty { "None" },
|
||||
icon = Icons.Outlined.Key,
|
||||
onClick = { hmacOpenDialog = true }
|
||||
)
|
||||
}
|
||||
if (hmacOpenDialog) {
|
||||
NPSAlertDialog(
|
||||
onDismiss = { hmacOpenDialog = false },
|
||||
title = "HMAC Key",
|
||||
buttons = {
|
||||
TextButton(onClick = { hmacOpenDialog = false }) {
|
||||
Text(text = "Cancel")
|
||||
}
|
||||
TextButton(onClick = {
|
||||
viewModel.setHMACKey(dialogHmacKey)
|
||||
hmacOpenDialog = false
|
||||
}) {
|
||||
Text(text = "OK")
|
||||
}
|
||||
}
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = dialogHmacKey,
|
||||
onValueChange = { dialogHmacKey = it },
|
||||
label = { Text(text = "HMAC Key") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
trailingIcon = {
|
||||
NPSIconButton(
|
||||
tooltip = "Paste",
|
||||
onClick = { dialogHmacKey = clipboard.getText()?.text ?: "" }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ContentPaste,
|
||||
contentDescription = "Paste",
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Downloads(viewModel: SettingsViewModel, prefs: SettingsPreferences) {
|
||||
val downloadDirPicker = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocumentTree(),
|
||||
) { dirUri ->
|
||||
if (dirUri != null) {
|
||||
viewModel.setDownloadDir(dirUri.toString())
|
||||
}
|
||||
}
|
||||
|
||||
val unpackDirPicker = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocumentTree(),
|
||||
) { dirUri ->
|
||||
if (dirUri != null) {
|
||||
viewModel.setUnpackDir(dirUri.toString())
|
||||
}
|
||||
}
|
||||
|
||||
SettingGroup(title = "Downloads") {
|
||||
ToggleSetting(
|
||||
title = "Unpack in Download Directory",
|
||||
checked = prefs.unpackInDownload,
|
||||
onClick = {
|
||||
viewModel.setUnpackInDownload(it)
|
||||
}
|
||||
)
|
||||
ToggleSetting(
|
||||
title = "Delete package after unpacking",
|
||||
checked = prefs.deleteAfterUnpack,
|
||||
onClick = {
|
||||
viewModel.setDeleteAfterUnpack(it)
|
||||
}
|
||||
)
|
||||
DialogSetting(
|
||||
title = "Download Directory",
|
||||
value = Uri.decode(prefs.downloadDir).ifEmpty { "None" },
|
||||
icon = Icons.Outlined.FileDownload,
|
||||
onClick = {
|
||||
downloadDirPicker.launch(Uri.EMPTY)
|
||||
}
|
||||
)
|
||||
DialogSetting(
|
||||
title = "Unpack Directory",
|
||||
value = Uri.decode(prefs.unpackDir).ifEmpty { "None" },
|
||||
icon = Icons.Outlined.Unarchive,
|
||||
enabled = !prefs.unpackInDownload,
|
||||
onClick = {
|
||||
unpackDirPicker.launch(Uri.EMPTY)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Pkg2Zip(viewModel: SettingsViewModel, prefs: SettingsPreferences) {
|
||||
var pkg2zipOpenDialog by remember { mutableStateOf(false) }
|
||||
var dialogPkg2zipParams by remember { mutableStateOf("") }
|
||||
|
||||
SideEffect {
|
||||
dialogPkg2zipParams = prefs.pkg2zipParams
|
||||
}
|
||||
|
||||
SettingGroup(title = "Pkg2Zip") {
|
||||
ToggleSetting(
|
||||
title = "Automatically decrypt downloaded content",
|
||||
checked = prefs.pkg2zipAutoDecrypt,
|
||||
onClick = {
|
||||
viewModel.setPkg2zipAutoDecrypt(it)
|
||||
}
|
||||
)
|
||||
DialogSetting(
|
||||
title = "Decryption params",
|
||||
value = prefs.pkg2zipParams.ifEmpty { "None" },
|
||||
icon = painterResource(
|
||||
id = R.drawable.lock_open
|
||||
),
|
||||
onClick = { pkg2zipOpenDialog = true }
|
||||
)
|
||||
}
|
||||
if (pkg2zipOpenDialog) {
|
||||
NPSAlertDialog(
|
||||
onDismiss = { pkg2zipOpenDialog = false },
|
||||
title = "PKG2ZIP Args",
|
||||
buttons = {
|
||||
TextButton(onClick = { pkg2zipOpenDialog = false }) {
|
||||
Text(text = "Cancel")
|
||||
}
|
||||
TextButton(onClick = {
|
||||
viewModel.setPkg2zipParams(dialogPkg2zipParams)
|
||||
pkg2zipOpenDialog = false
|
||||
}) {
|
||||
Text(text = "OK")
|
||||
}
|
||||
}
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = dialogPkg2zipParams,
|
||||
onValueChange = { dialogPkg2zipParams = it },
|
||||
label = { Text(text = "PKG2ZIP Args") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ThemeDialog(
|
||||
prefs: SettingsPreferences,
|
||||
onDismiss: () -> Unit,
|
||||
onThemeChange: (v: Theme) -> Unit
|
||||
) {
|
||||
NPSAlertDialog(
|
||||
onDismiss = onDismiss,
|
||||
title = stringResource(id = R.string.theme),
|
||||
buttons = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(text = "Cancel")
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column {
|
||||
NPSRadioButton(
|
||||
text = "Light",
|
||||
onClick = {
|
||||
onThemeChange(Theme.LIGHT)
|
||||
},
|
||||
selected = prefs.appTheme == Theme.LIGHT.ordinal,
|
||||
)
|
||||
NPSRadioButton(
|
||||
text = "Dark",
|
||||
onClick = {
|
||||
onThemeChange(Theme.DARK)
|
||||
},
|
||||
selected = prefs.appTheme == Theme.DARK.ordinal,
|
||||
)
|
||||
NPSRadioButton(
|
||||
text = "Use System Theme",
|
||||
onClick = {
|
||||
onThemeChange(Theme.SYSTEM)
|
||||
},
|
||||
selected = prefs.appTheme == Theme.SYSTEM.ordinal,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingGroup(title: String, content: @Composable () -> Unit) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Header(title)
|
||||
Spacer(modifier = Modifier.size(2.dp))
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Header(title: String) {
|
||||
Row(Modifier.padding(start = 56.dp)) {
|
||||
Text(text = title, color = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
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)
|
||||
) {
|
||||
if (icon != null) {
|
||||
Icon(painter = icon, contentDescription = null, modifier = Modifier.width(56.dp))
|
||||
} else {
|
||||
Box(modifier = Modifier.width(56.dp))
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(text = title, color = MaterialTheme.colorScheme.onBackground)
|
||||
Text(text = value, color = ColorSecondaryLight, fontSize = 14.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DialogSetting(
|
||||
title: String,
|
||||
value: String,
|
||||
icon: ImageVector?,
|
||||
onClick: () -> Unit,
|
||||
singleLine : Boolean = false,
|
||||
enabled: Boolean = true
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.alpha(if (enabled) 1f else 0.5f)
|
||||
.then(if (enabled) Modifier.clickable { onClick() } else Modifier)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
) {
|
||||
if (icon != null) {
|
||||
Icon(imageVector = icon, contentDescription = null, modifier = Modifier.width(56.dp))
|
||||
} else {
|
||||
Box(modifier = Modifier.width(56.dp))
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(text = title, color = MaterialTheme.colorScheme.onBackground)
|
||||
Text(
|
||||
text = value,
|
||||
color = ColorSecondaryLight,
|
||||
fontSize = 14.sp,
|
||||
maxLines = if (singleLine) 1 else Int.MAX_VALUE,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(end = 24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToggleSetting(title: String, icon: ImageVector? = null, checked: Boolean, onClick: (Boolean) -> Unit) {
|
||||
Box(
|
||||
modifier = Modifier.clickable onClick@{
|
||||
onClick(!checked)
|
||||
},
|
||||
) {
|
||||
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 = null, 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 = checked, onCheckedChange = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
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)
|
||||
@@ -1,122 +0,0 @@
|
||||
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()
|
||||
window.navigationBarColor = 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,
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.illusionware.npsbrowser.util
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
|
||||
// Borrowed from https://github.com/tachiyomiorg/tachiyomi/tree/master
|
||||
fun Context.isNavigationBarNeedsScrim(): Boolean {
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ||
|
||||
InternalResourceHelper.getBoolean(this, "config_navBarNeedsScrim", true)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.illusionware.npsbrowser.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
|
||||
// Borrowed from https://github.com/tachiyomiorg/tachiyomi/tree/master
|
||||
object InternalResourceHelper {
|
||||
fun getBoolean(context: Context, resName: String, defaultValue: Boolean): Boolean {
|
||||
val id = getResourceId(resName, "bool")
|
||||
return if (id != 0) {
|
||||
context.createPackageContext("android", 0).resources.getBoolean(id)
|
||||
} else {
|
||||
defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resource id from system resources
|
||||
* @param resName resource name to get
|
||||
* @param type resource type of [resName] to get
|
||||
* @return 0 if not available
|
||||
*/
|
||||
@SuppressLint("DiscouragedApi")
|
||||
private fun getResourceId(resName: String, type: String): Int {
|
||||
return Resources.getSystem().getIdentifier(resName, type, "android")
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.illusionware.npsbrowser.viewmodels
|
||||
|
||||
import android.os.Environment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.illusionware.npsbrowser.NPSApp
|
||||
import com.illusionware.npsbrowser.data.OnboardingPreferences
|
||||
import com.illusionware.npsbrowser.data.OnboardingPreferencesRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
class OnboardingViewModel(
|
||||
private val onboardingPreferencesRepository: OnboardingPreferencesRepository
|
||||
): ViewModel() {
|
||||
val uiState: StateFlow<OnboardingPreferences> =
|
||||
onboardingPreferencesRepository.getPreferences().stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(2_000),
|
||||
OnboardingPreferences(
|
||||
false,
|
||||
)
|
||||
)
|
||||
|
||||
fun finishOnboarding(shouldCreateDefaultDir: Boolean) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
if (shouldCreateDefaultDir) {
|
||||
val dir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "NPSBrowser")
|
||||
if (!dir.exists()) {
|
||||
dir.mkdir()
|
||||
}
|
||||
}
|
||||
onboardingPreferencesRepository.setSeenOnboarding(true)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
val application = (this[APPLICATION_KEY] as NPSApp)
|
||||
OnboardingViewModel(application.onboardingPreferencesRepository)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.illusionware.npsbrowser.viewmodels
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.illusionware.npsbrowser.model.PackageItem
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
data class SelectedPackageUiState(
|
||||
val item: PackageItem?,
|
||||
)
|
||||
|
||||
class PackageDetailsViewModel: ViewModel() {
|
||||
private val _uiState = MutableStateFlow(SelectedPackageUiState(null))
|
||||
val uiState: StateFlow<SelectedPackageUiState> = _uiState.asStateFlow()
|
||||
|
||||
fun setSelectedPackage(item: PackageItem) {
|
||||
_uiState.value = SelectedPackageUiState(item)
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
package com.illusionware.npsbrowser.viewmodels
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.github.doyaaaaaken.kotlincsv.dsl.context.ExcessFieldsRowBehaviour
|
||||
import com.github.doyaaaaaken.kotlincsv.dsl.context.InsufficientFieldsRowBehaviour
|
||||
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
|
||||
import com.illusionware.npsbrowser.NPSApp
|
||||
import com.illusionware.npsbrowser.data.SettingsPreferencesRepository
|
||||
import com.illusionware.npsbrowser.model.ConsoleType
|
||||
import com.illusionware.npsbrowser.model.PackageItem
|
||||
import com.illusionware.npsbrowser.model.PackageItemType
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.internal.toLongOrDefault
|
||||
import java.io.InputStream
|
||||
|
||||
data class PackageListUiState(
|
||||
val packages: ArrayList<PackageItem> = ArrayList(),
|
||||
val error: String = "",
|
||||
val isLoading: Boolean = true,
|
||||
)
|
||||
|
||||
class PackageListViewModel(app: NPSApp, settingsRepo: SettingsPreferencesRepository): ViewModel() {
|
||||
private val _uiState = MutableStateFlow(PackageListUiState())
|
||||
val uiState: StateFlow<PackageListUiState> = _uiState.asStateFlow()
|
||||
|
||||
private val tsvReader = csvReader {
|
||||
charset = "UTF-8"
|
||||
quoteChar = '"'
|
||||
delimiter = '\t'
|
||||
excessFieldsRowBehaviour = ExcessFieldsRowBehaviour.TRIM
|
||||
insufficientFieldsRowBehaviour = InsufficientFieldsRowBehaviour.EMPTY_STRING
|
||||
}
|
||||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val list = ArrayList<PackageItem>()
|
||||
|
||||
settingsRepo.availableTsvs().forEach {
|
||||
list += parsePackageFromTSV(it, app)
|
||||
}
|
||||
|
||||
list.sortBy { it.name }
|
||||
|
||||
_uiState.emit(PackageListUiState(isLoading = false, packages = list))
|
||||
|
||||
settingsRepo.tsvFiles().collect { tsv ->
|
||||
_uiState.emit(PackageListUiState())
|
||||
|
||||
val alreadyExists = list.find { item ->
|
||||
item.consoleType == tsv.second && item.dataType == tsv.third
|
||||
}
|
||||
|
||||
if (alreadyExists != null) {
|
||||
list.removeAll { item ->
|
||||
item.consoleType == tsv.second && item.dataType == tsv.third
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(parsePackageFromTSV(tsv, app))
|
||||
list.sortBy { it.name }
|
||||
|
||||
_uiState.emit(PackageListUiState(isLoading = false, packages = list))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parsePackageFromTSV(
|
||||
it: Triple<String, ConsoleType, PackageItemType>, app: NPSApp
|
||||
): ArrayList<PackageItem> {
|
||||
val list = ArrayList<PackageItem>()
|
||||
|
||||
val fileUri = it.first
|
||||
val console = it.second
|
||||
val type = it.third
|
||||
|
||||
val file: InputStream?
|
||||
try {
|
||||
file = app.contentResolver.openInputStream(Uri.parse(fileUri))
|
||||
} catch (e: Exception) {
|
||||
throw e
|
||||
}
|
||||
val tsv = tsvReader.readAllWithHeader(file!!)
|
||||
|
||||
tsv.forEach { row ->
|
||||
if (row["Name"].isNullOrEmpty()) {
|
||||
return@forEach
|
||||
}
|
||||
var size = ""
|
||||
var minFW: String? = null
|
||||
var pkgUrl: String? = null
|
||||
var zRif: String? = null
|
||||
var rap: String? = null
|
||||
var contentId: String? = null
|
||||
var sha256: String? = null
|
||||
var modificationDate: String? = null
|
||||
|
||||
val tempSize = row["File Size"]
|
||||
val tempFW = row["Required FW"]
|
||||
val tempUrl = row["PKG direct link"]
|
||||
val tempZRif = row["zRIF"]
|
||||
val tempRap = row["RAP"]
|
||||
val tempContentId = row["Content ID"]
|
||||
val tempsha256 = row["SHA256"]
|
||||
val tempDate = row["Last Modification Date"]
|
||||
if (!tempSize.isNullOrEmpty()) {
|
||||
size = bytesIntoHumanReadable(tempSize.toLongOrDefault(0))
|
||||
}
|
||||
if (!tempFW.isNullOrEmpty()) {
|
||||
minFW = String.format("%.2f", tempFW.toFloat())
|
||||
}
|
||||
if (!tempUrl.isNullOrEmpty() && tempUrl != "MISSING") {
|
||||
pkgUrl = tempUrl
|
||||
}
|
||||
if (!tempZRif.isNullOrEmpty() && tempZRif != "MISSING") {
|
||||
zRif = tempZRif
|
||||
}
|
||||
if (!tempRap.isNullOrEmpty() && tempRap != "MISSING") {
|
||||
rap = tempRap
|
||||
}
|
||||
if (!tempContentId.isNullOrEmpty()) {
|
||||
contentId = tempContentId
|
||||
}
|
||||
if (!tempsha256.isNullOrEmpty()) {
|
||||
sha256 = tempsha256
|
||||
}
|
||||
if (!tempDate.isNullOrEmpty()) {
|
||||
modificationDate = tempDate
|
||||
}
|
||||
|
||||
list.add(
|
||||
PackageItem(
|
||||
row["Title ID"] ?: "",
|
||||
row["Region"] ?: "",
|
||||
row["Name"]?.trim() ?: "",
|
||||
pkgUrl,
|
||||
contentId,
|
||||
modificationDate,
|
||||
size,
|
||||
sha256,
|
||||
zRif,
|
||||
rap,
|
||||
minFW,
|
||||
console,
|
||||
type,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
file.close()
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
private fun bytesIntoHumanReadable(bytes: Long): String {
|
||||
val kilobyte: Long = 1024
|
||||
val megabyte = kilobyte * 1024
|
||||
val gigabyte = megabyte * 1024
|
||||
val terabyte = gigabyte * 1024
|
||||
|
||||
return if (bytes in 0 until kilobyte) {
|
||||
"$bytes" + "B"
|
||||
} else if (bytes in kilobyte until megabyte) {
|
||||
String.format("%.2f", (bytes.toDouble() / kilobyte.toDouble())) + "KB"
|
||||
} else if (bytes in megabyte until gigabyte) {
|
||||
String.format("%.2f", (bytes.toDouble() / megabyte.toDouble())) + "MB"
|
||||
} else if (bytes in gigabyte until terabyte) {
|
||||
String.format("%.2f", (bytes.toDouble() / gigabyte.toDouble())) + "GB"
|
||||
} else if (bytes >= terabyte) {
|
||||
String.format("%.2f", (bytes.toDouble() / terabyte.toDouble())) + "TB"
|
||||
} else {
|
||||
"$bytes" + "B"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
val application = (this[APPLICATION_KEY] as NPSApp)
|
||||
PackageListViewModel(application, application.settingsPreferencesRepository)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package com.illusionware.npsbrowser.viewmodels
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.illusionware.npsbrowser.NPSApp
|
||||
import com.illusionware.npsbrowser.data.ItemLayout
|
||||
import com.illusionware.npsbrowser.data.SettingsPreferences
|
||||
import com.illusionware.npsbrowser.data.SettingsPreferencesRepository
|
||||
import com.illusionware.npsbrowser.data.Theme
|
||||
import com.illusionware.npsbrowser.model.ConsoleType
|
||||
import com.illusionware.npsbrowser.model.PackageItemType
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesRepository): ViewModel() {
|
||||
val uiState: StateFlow<SettingsPreferences> =
|
||||
settingsPrefsRepo.getPreferences().stateIn(viewModelScope, SharingStarted.Lazily, SettingsPreferences(
|
||||
Theme.SYSTEM.ordinal,
|
||||
ItemLayout.LIST.ordinal,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
"",
|
||||
true,
|
||||
))
|
||||
|
||||
fun availableTsvs(): MutableList<Pair<ConsoleType, PackageItemType>> {
|
||||
val list = mutableListOf<Pair<ConsoleType, PackageItemType>>()
|
||||
if (uiState.value.psvGames.isNotEmpty()) list.add(Pair(ConsoleType.PSVITA, PackageItemType.GAME))
|
||||
if (uiState.value.ps3Games.isNotEmpty()) list.add(Pair(ConsoleType.PS3, PackageItemType.GAME))
|
||||
if (uiState.value.pspGames.isNotEmpty()) list.add(Pair(ConsoleType.PSP, PackageItemType.GAME))
|
||||
if (uiState.value.psxGames.isNotEmpty()) list.add(Pair(ConsoleType.PSX, PackageItemType.GAME))
|
||||
if (uiState.value.psmGames.isNotEmpty()) list.add(Pair(ConsoleType.PSM, PackageItemType.GAME))
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
suspend fun getAppTheme(): Int {
|
||||
return settingsPrefsRepo.getPreferences().first().appTheme
|
||||
}
|
||||
|
||||
fun shouldCreateDefaultDir(): Boolean {
|
||||
val defaultDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + "/NPSBrowser"
|
||||
|
||||
return uiState.value.downloadDir == defaultDir
|
||||
|| uiState.value.unpackDir == defaultDir
|
||||
}
|
||||
|
||||
fun setTSVFile(console: ConsoleType, itemType: PackageItemType, file: Uri) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
settingsPrefsRepo.setTSVFile(console, itemType, file)
|
||||
}
|
||||
}
|
||||
|
||||
fun setTheme(theme: Theme) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
settingsPrefsRepo.setTheme(theme)
|
||||
}
|
||||
}
|
||||
|
||||
fun setLayout(layout: ItemLayout) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
settingsPrefsRepo.setLayout(layout)
|
||||
}
|
||||
}
|
||||
|
||||
fun setHMACKey(hmacKey: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
settingsPrefsRepo.setHMACKey(hmacKey)
|
||||
}
|
||||
}
|
||||
|
||||
fun setDownloadDir(downloadDir: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
settingsPrefsRepo.setDownloadDir(downloadDir)
|
||||
}
|
||||
}
|
||||
|
||||
fun setUnpackDir(unpackDir: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
settingsPrefsRepo.setUnpackDir(unpackDir)
|
||||
}
|
||||
}
|
||||
|
||||
fun setUnpackInDownload(unpackInDownload: Boolean) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
settingsPrefsRepo.setUnpackInDownload(unpackInDownload)
|
||||
}
|
||||
}
|
||||
|
||||
fun setDeleteAfterUnpack(deleteAfterUnpack: Boolean) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
settingsPrefsRepo.setDeleteAfterUnpack(deleteAfterUnpack)
|
||||
}
|
||||
}
|
||||
|
||||
fun setPkg2zipParams(pkg2zipParams: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
settingsPrefsRepo.setPkg2zipParams(pkg2zipParams)
|
||||
}
|
||||
}
|
||||
|
||||
fun setPkg2zipAutoDecrypt(pkg2zipAutoDecrypt: Boolean) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
settingsPrefsRepo.setPkg2zipAutoDecrypt(pkg2zipAutoDecrypt)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
val application = (this[APPLICATION_KEY] as NPSApp)
|
||||
SettingsViewModel(application.settingsPreferencesRepository)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
||||
<solid android:color="@android:color/holo_blue_dark" />
|
||||
<stroke android:width="2dip" android:color="@android:color/holo_blue_dark"/>
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
||||
<solid android:color="@android:color/holo_red_dark" />
|
||||
<stroke android:width="2dip" android:color="@android:color/holo_red_dark"/>
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
||||
|
||||
<!-- The background color -->
|
||||
<item android:drawable="@drawable/splash_gradient"/>
|
||||
|
||||
<!-- App logo -->
|
||||
<item
|
||||
android:drawable="@drawable/ic_nps_fg_no_shadow"
|
||||
android:gravity="center"
|
||||
android:width="400dp"
|
||||
android:height="400dp" />
|
||||
|
||||
</layer-list>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,20 +0,0 @@
|
||||
<vector android:height="256dp" android:viewportHeight="100"
|
||||
android:viewportWidth="100" android:width="256dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#ee3e54" android:pathData="M13,27A2,2 0,1 0,13 31A2,2 0,1 0,13 27Z"/>
|
||||
<path android:fillColor="#f1bc19" android:pathData="M77,12A1,1 0,1 0,77 14A1,1 0,1 0,77 12Z"/>
|
||||
<path android:fillColor="#fce0a2" android:pathData="M50,13A37,37 0,1 0,50 87A37,37 0,1 0,50 13Z"/>
|
||||
<path android:fillColor="#f1bc19" android:pathData="M83,11A4,4 0,1 0,83 19A4,4 0,1 0,83 11Z"/>
|
||||
<path android:fillColor="#ee3e54" android:pathData="M87,22A2,2 0,1 0,87 26A2,2 0,1 0,87 22Z"/>
|
||||
<path android:fillColor="#fbcd59" android:pathData="M81,74A2,2 0,1 0,81 78,2 2,0 1,0 81,74zM15,59A4,4 0,1 0,15 67,4 4,0 1,0 15,59z"/>
|
||||
<path android:fillColor="#ee3e54" android:pathData="M25,85A2,2 0,1 0,25 89A2,2 0,1 0,25 85Z"/>
|
||||
<path android:fillColor="#fff" android:pathData="M18.5,51A2.5,2.5 0,1 0,18.5 56A2.5,2.5 0,1 0,18.5 51Z"/>
|
||||
<path android:fillColor="#f1bc19" android:pathData="M21,66A1,1 0,1 0,21 68A1,1 0,1 0,21 66Z"/>
|
||||
<path android:fillColor="#fff" android:pathData="M80,33A1,1 0,1 0,80 35A1,1 0,1 0,80 33Z"/>
|
||||
<path android:fillColor="#cdcbbd" android:pathData="M29.51,69.3c-0.5,0 -1.13,-0.14 -1.48,-1.27c-0.27,-0.87 -0.27,-1.95 -0.27,-2.65L27.7,37.8v-3.82c0,-1.69 1.32,-3.07 2.94,-3.07h17.65c1.66,0 3.01,1.46 3.01,3.24v1.46c0,1.24 0.97,2.26 2.16,2.26h16.09c0.42,0 0.76,0.38 0.76,0.86v23.57c0,0.33 0.01,0.75 0.03,1.21c0.05,1.7 0.13,4.46 -0.44,5.8H29.51z"/>
|
||||
<path android:fillColor="#472b29" android:pathData="M48.29,31.6c1.27,0 2.31,1.14 2.31,2.54v1.46c0,1.63 1.28,2.96 2.86,2.96l16.08,-0c0.01,0 0.07,0.05 0.07,0.16v23.57c0,0.34 0.01,0.76 0.03,1.23c0.04,1.29 0.11,3.74 -0.24,5.08h-39.88c-0.27,0 -0.57,0 -0.81,-0.79c-0.24,-0.77 -0.24,-1.78 -0.24,-2.44l0,-0.09L28.4,37.8v-3.82c0,-1.31 1.01,-2.37 2.24,-2.37H48.29M48.29,30.2H30.65c-2.01,0 -3.64,1.7 -3.64,3.77v3.82l0.05,27.48C27.05,66.7 27,70 29.51,70h40.8C71.37,68.36 71,64.1 71,62.28V38.72c0,-0.86 -0.65,-1.56 -1.46,-1.56H53.46c-0.81,0 -1.46,-0.7 -1.46,-1.56v-1.46C52,31.98 50.33,30.2 48.29,30.2L48.29,30.2z"/>
|
||||
<path android:fillColor="#472b29" android:pathData="M41.5,35h-11c-0.28,0 -0.5,-0.22 -0.5,-0.5s0.22,-0.5 0.5,-0.5h11c0.28,0 0.5,0.22 0.5,0.5S41.78,35 41.5,35z"/>
|
||||
<path android:fillColor="#472b29" android:pathData="M48.5,35h-4c-0.28,0 -0.5,-0.22 -0.5,-0.5s0.22,-0.5 0.5,-0.5h4c0.28,0 0.5,0.22 0.5,0.5S48.78,35 48.5,35z"/>
|
||||
<path android:fillColor="#fdfcee" android:pathData="M29.48,69.3c-0.61,0 -1.11,-0.52 -1.14,-1.18L34.03,42.7h41.92l-5.64,25.37c-0.01,0.05 -0.02,0.1 -0.02,0.15c0,0.29 -0.11,0.57 -0.3,0.77c-0.18,0.19 -0.43,0.3 -0.68,0.3H29.48z"/>
|
||||
<path android:fillColor="#472b29" android:pathData="M75.07,43.4l-5.45,24.52c-0.02,0.1 -0.03,0.2 -0.03,0.31c0,0.12 -0.04,0.23 -0.12,0.3c-0.05,0.05 -0.11,0.08 -0.17,0.08H29.48c-0.21,0 -0.39,-0.19 -0.43,-0.44L34.59,43.4H75.07M76.82,42H33.47l-5.83,26.05c0,1.08 0.82,1.95 1.84,1.95h39.83c0.93,0 1.68,-0.8 1.68,-1.78L76.82,42L76.82,42z"/>
|
||||
<path android:fillColor="#472b29" android:pathData="M50.5,55h-14c-0.15,0 -0.3,-0.07 -0.39,-0.19s-0.13,-0.28 -0.09,-0.43l1.5,-6C37.57,48.16 37.77,48 38,48h14c0.15,0 0.3,0.07 0.39,0.19s0.13,0.28 0.09,0.43l-1.5,6C50.93,54.84 50.73,55 50.5,55zM37.14,54h12.97l1.25,-5H38.39L37.14,54z"/>
|
||||
</vector>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFCA00"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z"/>
|
||||
</vector>
|
||||
@@ -1,7 +0,0 @@
|
||||
<vector android:height="24dp" android:viewportHeight="256"
|
||||
android:viewportWidth="256" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#000000"
|
||||
android:pathData="m139.8,13.09a72.78,72.78 0,0 0,-69.17 95.25l-52.33,52.31a7.27,7.27 0,0 0,-2.13 5.16v36.36a7.27,7.27 0,0 0,7.27 7.27h12.95v-24.08a7.27,7.27 0,0 1,2.14 -5.14l52.31,-52.33A72.78,72.78 0,0 1,160.02 32.66,72.72 72.72,0 0,1 194.86,43 72.72,72.72 0,0 0,139.8 13.09ZM178.52,73.52a14.54,14.54 0,0 0,-9.95 3.66,14.54 14.54,0 0,1 -0.27,0.64 14.54,14.54 0,0 0,5.89 22,15.7 15.7,0 0,1 -0.98,-4.92 15.7,15.7 0,0 1,15.7 -15.69,15.7 15.7,0 0,1 0.16,0.02 14.54,14.54 0,0 0,-10.55 -5.7z" android:strokeWidth="0.908993"/>
|
||||
<path android:fillColor="#000000"
|
||||
android:pathData="M169.69,40.2A72.78,72.78 0,0 0,100.52 135.45l-52.33,52.32a7.27,7.27 0,0 0,-2.13 5.14v36.36a7.27,7.27 0,0 0,7.27 7.27h36.36a7.27,7.27 0,0 0,7.27 -7.27v-14.54h14.54a7.27,7.27 0,0 0,7.27 -7.27v-14.54h14.54a7.27,7.27 0,0 0,5.14 -2.13l8.69,-8.7A72.72,72.72 0,1 0,169.69 40.2ZM187.87,109.29a14.54,14.54 0,1 1,14.54 -14.54,14.54 14.54,0 0,1 -14.54,14.54z" android:strokeWidth="0.908993"/>
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<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>
|
||||
@@ -1,11 +0,0 @@
|
||||
<vector android:height="24dp" android:viewportHeight="12"
|
||||
android:viewportWidth="12" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h12v12h-12z"/>
|
||||
<path android:fillColor="#000000" android:pathData="M6,1C3.24,1 1,3.24 1,6C1,8.76 3.24,11 6,11C8.76,11 11,8.76 11,6C11,3.24 8.76,1 6,1ZM6,10C3.79,10 2,8.21 2,6C2,5.075 2.315,4.225 2.845,3.55L8.45,9.155C7.775,9.685 6.925,10 6,10ZM9.155,8.45L3.55,2.845C4.225,2.315 5.075,2 6,2C8.21,2 10,3.79 10,6C10,6.925 9.685,7.775 9.155,8.45Z"/>
|
||||
<group>
|
||||
<clip-path android:pathData="M2,2h8v8h-8z"/>
|
||||
<path android:fillColor="#000000" android:fillType="evenOdd" android:pathData="M5.2,4.8C5.2,4.38 5.332,3.972 5.577,3.631C5.823,3.291 6.169,3.036 6.567,2.903C6.965,2.77 7.395,2.766 7.795,2.891C8.196,3.016 8.547,3.264 8.799,3.599C9.051,3.934 9.191,4.341 9.2,4.76C9.208,5.18 9.084,5.591 8.846,5.936C8.608,6.282 8.267,6.543 7.872,6.684C7.477,6.825 7.047,6.838 6.644,6.722L5.883,7.483C5.808,7.558 5.706,7.6 5.6,7.6H5.2V8C5.2,8.106 5.158,8.208 5.083,8.283C5.008,8.358 4.906,8.4 4.8,8.4H4.4V8.8C4.4,8.906 4.358,9.008 4.283,9.083C4.208,9.158 4.106,9.2 4,9.2H3.2C3.094,9.2 2.992,9.158 2.917,9.083C2.842,9.008 2.8,8.906 2.8,8.8V8C2.8,7.894 2.842,7.792 2.917,7.717L5.278,5.356C5.226,5.175 5.2,4.988 5.2,4.8ZM7.2,3.6C7.12,3.6 7.044,3.632 6.988,3.688C6.932,3.744 6.9,3.82 6.9,3.9C6.9,3.98 6.932,4.056 6.988,4.112C7.044,4.168 7.12,4.2 7.2,4.2C7.359,4.2 7.512,4.263 7.624,4.376C7.737,4.488 7.8,4.641 7.8,4.8C7.8,4.88 7.832,4.956 7.888,5.012C7.944,5.068 8.02,5.1 8.1,5.1C8.18,5.1 8.256,5.068 8.312,5.012C8.368,4.956 8.4,4.88 8.4,4.8C8.4,4.482 8.274,4.177 8.049,3.951C7.823,3.726 7.518,3.6 7.2,3.6Z"/>
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -1,15 +0,0 @@
|
||||
<vector android:height="24dp" android:viewportHeight="12"
|
||||
android:viewportWidth="12" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h12v12h-12z"/>
|
||||
<path android:fillColor="#000000" android:pathData="M6,1C3.24,1 1,3.24 1,6C1,8.76 3.24,11 6,11C8.76,11 11,8.76 11,6C11,3.24 8.76,1 6,1ZM6,10C3.79,10 2,8.21 2,6C2,5.075 2.315,4.225 2.845,3.55L8.45,9.155C7.775,9.685 6.925,10 6,10ZM9.155,8.45L3.55,2.845C4.225,2.315 5.075,2 6,2C8.21,2 10,3.79 10,6C10,6.925 9.685,7.775 9.155,8.45Z"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M5.822,4.231L6.441,3.612C6.703,3.374 7.046,3.245 7.401,3.253C7.755,3.261 8.093,3.406 8.343,3.657C8.594,3.907 8.739,4.245 8.747,4.599C8.755,4.953 8.626,5.297 8.387,5.559L7.503,6.441C7.376,6.569 7.224,6.67 7.057,6.739C6.891,6.809 6.712,6.844 6.531,6.844C6.351,6.844 6.172,6.809 6.005,6.739C5.838,6.67 5.687,6.569 5.559,6.441"
|
||||
android:strokeColor="#000000" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M6.178,7.769L5.559,8.387C5.297,8.626 4.954,8.755 4.599,8.747C4.245,8.739 3.907,8.594 3.657,8.343C3.406,8.093 3.261,7.755 3.253,7.401C3.245,7.047 3.374,6.703 3.613,6.441L4.497,5.559C4.624,5.431 4.776,5.33 4.943,5.261C5.109,5.191 5.288,5.156 5.469,5.156C5.649,5.156 5.828,5.191 5.995,5.261C6.162,5.33 6.313,5.431 6.441,5.559"
|
||||
android:strokeColor="#000000" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="1"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -1,118 +0,0 @@
|
||||
<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>
|
||||
@@ -1,4 +0,0 @@
|
||||
<vector android:height="24dp" android:viewportHeight="256"
|
||||
android:viewportWidth="256" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#000000" android:pathData="M223.68,66.15 L135.68,18a15.88,15.88 0,0 0,-15.36 0l-88,48.17a16,16 0,0 0,-8.32 14v95.64a16,16 0,0 0,8.32 14l88,48.17a15.88,15.88 0,0 0,15.36 0l88,-48.17a16,16 0,0 0,8.32 -14L232,80.18A16,16 0,0 0,223.68 66.15ZM128,32l80.35,44L178.57,92.29l-80.35,-44ZM128,120L47.65,76 81.56,57.43l80.35,44ZM216,175.85h0l-80,43.79L136,133.83l32,-17.51L168,152a8,8 0,0 0,16 0L184,107.56l32,-17.51v85.76Z"/>
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<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>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector android:height="24dp" android:viewportHeight="425.72"
|
||||
android:viewportWidth="425.72" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#000000"
|
||||
android:pathData="m70.82,87.19c-6.6,1.9 -7.7,2.7 -9.1,6.9 -0.5,1.8 -2.3,4 -4,4.9 -4.3,2.6 -5.6,5.9 -7,17.9 -1.2,9.7 -1.5,10.7 -3.8,11.9 -4.9,2.7 -15,14.9 -19.5,23.7 -7.3,14.2 -9.7,23.8 -17.6,71.7 -8.9,53.3 -10.3,63.7 -9.7,71.5 1.1,16.1 9.2,31.1 20,37.5 13.7,8 31.4,9.1 44.9,2.7 10.9,-5.1 14.4,-9.5 36.1,-45l11.8,-19.3 5.5,6.1c3,3.3 7.5,7.2 9.9,8.6 22,12.9 50.7,5.2 62.3,-16.6l3.2,-6l20.4,-0 20.5,-0l2.6,5.1c3.2,6.4 10.4,14.2 16.2,17.6 17.9,10.5 41.6,7.3 55.3,-7.5 2.2,-2.3 4.1,-4.2 4.3,-4.2 0.2,-0 7,10.3 15,22.8 8,12.6 16.2,24.7 18.2,27 8,9.4 18.8,14.2 32,14.2 24.4,-0 39.7,-11.3 45.4,-33.6 1.1,-4.2 2,-9.7 2,-12.1 0,-9.3 -14.7,-106.4 -18.5,-122.1 -0.9,-3.7 -3.7,-11 -6.2,-16.2 -3.7,-7.7 -6,-10.8 -12.2,-17 -4.2,-4.2 -8.6,-8.3 -9.9,-9.1 -2,-1.2 -2.5,-2.8 -3.8,-12.9 -1.7,-12.7 -2.6,-14.8 -8.3,-18.6 -2.2,-1.4 -4.2,-3.6 -4.5,-4.8 -1,-4.1 -10.3,-6.6 -24.6,-6.6 -14,-0 -23.4,2.5 -24.6,6.4 -0.4,1.1 -2.6,3.8 -5,5.9 -4.7,4.1 -5.7,6.6 -7.4,17.7 -0.6,4.1 -1.3,8.5 -1.6,9.7l-0.4,2.3l-36.1,-0c-31.7,-0 -36.4,0.2 -39.2,1.7 -4.4,2.2 -16.9,2.3 -22.1,-0 -5.2,-2.3 -18.6,-2.4 -18.6,-0.2 0,0.8 -0.9,1.5 -2,1.5 -1.1,-0 -2,-0.7 -2,-1.5 0,-1.1 -1.2,-1.5 -4.5,-1.5 -3.3,-0 -4.5,0.4 -4.5,1.5 0,0.8 -0.7,1.5 -1.5,1.5 -0.8,-0 -1.5,-0.7 -1.5,-1.5 0,-1.1 -1.2,-1.5 -4.4,-1.5 -3,-0 -4.6,0.5 -5,1.5 -0.3,0.8 -1.2,1.5 -2.1,1.5 -0.8,-0 -1.5,-0.7 -1.5,-1.5 0,-1.1 -1.2,-1.5 -4.5,-1.5 -3.3,-0 -4.5,0.4 -4.5,1.5 0,0.8 -0.9,1.5 -2,1.5 -1.1,-0 -2,-0.7 -2,-1.5 0,-1.2 -1.4,-1.5 -6.5,-1.5 -7.4,-0 -6.6,1.3 -8.9,-13.6 -1.6,-10.1 -3.2,-13.3 -8.1,-16.4 -1.8,-1.1 -4.2,-3.5 -5.3,-5.3 -2.6,-4.6 -7.3,-5.9 -22.7,-6.3 -9.5,-0.3 -14.4,-0 -18.4,1.1zM102.72,122.29c3.6,0.8 10.3,3.3 15,5.6 11.9,5.7 23.8,17.7 29.8,29.9 6.7,13.6 8.8,28.2 6.1,43.6 -0.9,5.3 -1.4,6.3 -3.1,6.3 -1.7,-0 -1.9,-0.4 -1.3,-2.3 1.6,-5.2 1.7,-25.1 0.1,-31.9 -5.4,-23 -24.1,-41.9 -46.9,-47.3 -8,-1.9 -22.5,-1.9 -30.6,-0 -21.9,5.2 -41.4,24.7 -46.6,46.6 -1.9,8.2 -1.9,22.6 0.1,30.9 4.1,17.5 17.1,33.8 33.4,41.9 13.9,6.8 25.7,8.5 40.6,5.6 6.2,-1.2 8,-1.2 8.6,-0.2 1.5,2.4 -6.9,4.1 -20.7,4.1 -11.1,0.1 -14,-0.2 -20,-2.2 -17.5,-5.8 -32.6,-18.6 -40.3,-34.3 -9.6,-19.5 -9.7,-40.9 -0.1,-60.7 5.9,-12.2 17.9,-24.2 29.9,-30 4.7,-2.3 11.2,-4.8 14.5,-5.6 8,-1.9 23.4,-1.9 31.5,-0zM355.12,122.19c22,4.7 42.2,23.1 49.2,44.9 12.3,38.2 -11.3,78.8 -50.7,87.2 -5.2,1.1 -10.5,1.5 -17.9,1.1 -11.4,-0.5 -16,-1.8 -16,-4.3 0,-1.1 0.5,-1.4 1.6,-1 4.1,1.6 16.6,2.8 22.9,2.3 26.5,-2.3 48.8,-20.2 56.7,-45.7 3.4,-11 3.2,-26.4 -0.6,-37.7 -14.8,-45.3 -72.1,-59.1 -105.6,-25.4 -15.4,15.5 -22,37.1 -17.6,57.8 1.2,5.8 1.1,6.3 -0.4,6.3 -4.1,-0 -5.4,-23.3 -2,-36.4 3.1,-12.2 8.2,-20.9 18,-30.6 9.6,-9.7 18.3,-14.9 30.2,-18 8.9,-2.3 22.6,-2.5 32.2,-0.5zM347.42,141.79c13.7,6.6 8.8,27 -6.5,27.2 -8.5,0.1 -14.2,-5.6 -14.2,-14.4 0,-5.6 2.9,-10.4 8,-13 4.5,-2.4 7.4,-2.4 12.7,0.2zM89.72,146.19 L92.02,148.69l-5.9,-0 -5.9,-0l3,-2.5c3.7,-3 3.7,-3 6.5,-0zM96.52,164.09c0.3,7.1 0.1,8.1 -2,10.3 -2.4,2.6 -6.9,5.3 -8.7,5.3 -0.6,-0 -2.9,-1.6 -5.1,-3.6l-4,-3.6l0,-7.7c0,-4.3 0.3,-8.1 0.7,-8.5 0.4,-0.4 4.8,-0.5 9.8,-0.4l9,0.3zM381.62,174.49c10,5 10.6,18.6 1.2,24.9 -9.2,6.2 -22.1,-1 -22.1,-12.2 0,-10.7 11.3,-17.6 20.9,-12.7zM311.82,175.99c9.4,6.3 8.8,19.9 -1.2,24.9 -9.6,4.9 -20.9,-2 -20.9,-12.7 0,-11.2 12.9,-18.4 22.1,-12.2zM72.42,181.89c6,6.5 6,8.2 0,14.5 -2.9,3.1 -3.5,3.3 -9.8,3.3 -9,-0 -9.9,-1 -9.9,-10.5 0,-9.5 0.9,-10.5 9.9,-10.5 6.3,-0 6.9,0.2 9.8,3.2zM117.82,179.69c1.6,0.9 1.9,2.2 1.9,9.3 0,5.1 -0.5,8.8 -1.2,9.5 -0.7,0.7 -4,1.2 -7.9,1.2 -7.5,-0 -10.2,-1.5 -13.4,-7.3 -1.8,-3.4 -1.8,-3.5 0.5,-7.2 1.3,-2 3.7,-4.3 5.4,-5.1 3.7,-1.6 12,-1.8 14.7,-0.4zM188.52,182.29c2.4,1.7 3,7 1.2,10.5 -0.9,1.6 -2.2,1.9 -9.3,1.9 -5.1,-0 -8.8,-0.5 -9.5,-1.2 -1.9,-1.9 -1.5,-10.5 0.6,-11.7 2.8,-1.7 14.4,-1.3 17,0.5zM255.62,185.09c3.8,1.7 4.9,4.2 2.9,6.2 -2.3,2.4 -17,4.5 -20.1,3 -3.2,-1.7 -3.7,-9.8 -0.7,-11.9 1.9,-1.3 2.9,-1.3 8.8,-0 3.7,0.8 7.8,2 9.1,2.7zM131.42,191.49 L128.72,194.19l0,-5.8 0,-5.9l2.7,3.2 2.6,3.2zM45.72,190.29c0,5 -0.2,5.5 -1.6,4.4 -3.3,-2.8 -3.7,-5 -1.3,-7.6 1.2,-1.3 2.4,-2.4 2.6,-2.4 0.2,-0 0.3,2.5 0.3,5.6zM92.82,202.59c3.8,3.7 3.9,4.1 3.9,10.8 0,4 -0.5,7.4 -1.2,8.1 -0.7,0.7 -4.2,1.2 -8.8,1.2 -4.6,-0 -8.1,-0.5 -8.8,-1.2 -0.7,-0.7 -1.2,-4.1 -1.2,-8.4 0,-7.1 0,-7.2 4,-10.8 2.2,-2 5,-3.6 6.1,-3.6 1.2,-0 3.9,1.7 6,3.9zM221.42,204.79c12.1,7.5 6.5,26.9 -7.7,26.9 -5,-0 -10.7,-3.8 -13,-8.5 -2.5,-5.1 -2.5,-6.9 0,-12.2 3.5,-7.9 13.3,-10.8 20.7,-6.2zM349.22,209.09c10.6,8.1 4.4,25.6 -9,25.6 -7.8,-0 -13.6,-5.4 -14.3,-13.2 -0.3,-4.4 0,-5.9 2,-8.8 4.9,-7.3 14.3,-8.9 21.3,-3.6zM163.72,220.29c6.8,3.1 12.3,8.3 15.8,14.9 2.3,4.3 2.7,6.2 2.7,13.5 0,7.3 -0.4,9.2 -2.7,13.6 -7.2,13.8 -23.8,21.1 -38.4,17 -20.1,-5.8 -30.1,-26.6 -21.1,-44.2 8.1,-15.7 27.2,-22.2 43.7,-14.8zM292.32,220.19c20.8,9.4 25.7,36.2 9.2,51 -6.8,6.2 -14,8.9 -23.3,8.9 -9.9,-0 -16.7,-2.8 -23.7,-9.7 -8.1,-8.2 -11,-21 -7.2,-32.1 2,-5.8 10,-14.7 15.8,-17.6 9.3,-4.5 20,-4.7 29.2,-0.5zM90.72,232.29c-2.7,3.2 -4.6,3.4 -7,0.9l-2.3,-2.5l5.2,-0c4.7,-0 5.2,0.2 4.1,1.6z" android:strokeColor="#00000000"/>
|
||||
<path android:fillColor="#000000"
|
||||
android:pathData="m138.22,225.79c-5.1,2.5 -10.1,7.4 -12.8,12.4 -1.7,3.1 -2.2,5.8 -2.2,11.5 0,8.7 2,13.2 8.2,19.2 5.1,4.9 10.5,7 17.9,7 10.3,0.1 18.7,-5.1 23.7,-14.6 3,-5.8 3.1,-17.4 0,-23.1 -2.8,-5.2 -8.8,-11 -13.5,-13 -5.4,-2.2 -16.3,-1.9 -21.3,0.6z" android:strokeColor="#00000000"/>
|
||||
<path android:fillColor="#000000"
|
||||
android:pathData="m268.12,225.59c-15.5,7.1 -20.2,27.5 -9.4,41.1 6.8,8.6 21.4,11.9 31.3,7.1 8.8,-4.3 15.7,-14.9 15.7,-24.1 0,-9.2 -6.8,-19.6 -15.4,-23.6 -6.5,-3 -16.2,-3.2 -22.2,-0.5z" android:strokeColor="#00000000"/>
|
||||
</vector>
|
||||
@@ -1,11 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="29.424"
|
||||
android:viewportHeight="29.424">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m14.712,-0c-3.803,0 -7.607,0.701 -10.992,2.103 -0.704,0.292 -1.325,0.913 -1.617,1.617 -2.804,6.77 -2.804,15.214 0,21.984 0.292,0.704 0.913,1.325 1.617,1.617 6.77,2.804 15.214,2.804 21.984,0 0.704,-0.292 1.325,-0.913 1.617,-1.617 2.804,-6.77 2.804,-15.214 0,-21.984 -0.292,-0.704 -0.913,-1.325 -1.617,-1.617 -3.385,-1.402 -7.188,-2.103 -10.992,-2.103zM20.15,5.003c1.807,3.129 3.612,6.258 5.419,9.387l-10.839,0c1.806,-3.129 3.613,-6.258 5.419,-9.387zM5.277,5.652l8.719,0l0,8.719L5.277,14.371ZM6.678,7.052l0,5.918l5.918,0L12.596,7.052ZM20.15,7.869c-0.979,1.696 -1.959,3.392 -2.938,5.088l5.876,0c-0.979,-1.696 -1.959,-3.392 -2.938,-5.088zM20.14,14.938c0.039,-0 0.077,0 0.116,0.001 0.364,-0 0.727,0.041 1.082,0.123 2.378,0.501 4.09,3.008 3.664,5.406 -0.324,2.452 -2.777,4.359 -5.235,4.047 -2.465,-0.199 -4.49,-2.554 -4.305,-5.022 0.071,-2.433 2.252,-4.544 4.679,-4.555zM12.904,15.595c0.34,0.336 0.68,0.672 1.02,1.008 -1.085,1.098 -2.169,2.195 -3.254,3.293 1.097,1.085 2.194,2.169 3.291,3.254 -0.335,0.34 -0.671,0.68 -1.006,1.02l-3.293,-3.256 -3.256,3.293c-0.34,-0.336 -0.68,-0.672 -1.02,-1.008l3.256,-3.293c-1.098,-1.085 -2.195,-2.169 -3.293,-3.254 0.336,-0.34 0.672,-0.68 1.008,-1.02l3.293,3.254c1.085,-1.097 2.169,-2.194 3.254,-3.291zM20.347,16.447c-0.03,0 -0.061,0.001 -0.091,0.002 -1.242,-0.016 -2.443,0.735 -2.969,1.86 -0.891,1.722 0.112,4.06 1.987,4.576 1.838,0.634 4.008,-0.707 4.238,-2.642 0.356,-1.888 -1.252,-3.803 -3.164,-3.796z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeLineCap="square"/>
|
||||
</vector>
|
||||
@@ -1,6 +0,0 @@
|
||||
<vector android:height="24dp" android:viewportHeight="426.81"
|
||||
android:viewportWidth="426.81" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#000000"
|
||||
android:pathData="m73.19,122.41 l-14.23,14h-28.7,-4.43c-6.4,7.57 -10.74,14.82 -14.96,24.9 -12,28.5 -13.7,60.5 -4.5,88.3 5.4,16.2 20.4,39.7 32.2,50.3l5,4.5L196.27,304.4 348.87,304.4l9,-9 8.9,-9 17,-0.2 17,-0.3 3.8,-5c9.1,-11.9 17.3,-32 20.2,-49.4 5.55,-33.2 -4.23,-71.49 -23.84,-95.5L391.7,136l-9.93,0.2c-8.1,0.1 -15.3,0.1 -15.9,-0.2 -0.57,-0.19 -2.84,-2.48 -5.02,-5.08l-8.42,-8.52L213.47,122.41ZM87.37,133.4h124.5,124.5v72,72h-124.5,-124.5v-72zM381.24,162.24c6.86,0 12.42,5.56 12.42,12.42 0,6.86 -5.56,12.42 -12.42,12.42 -6.86,0 -12.42,-5.56 -12.42,-12.42 0,-6.86 5.56,-12.42 12.42,-12.42zM36.78,167.09h16.94c0.75,0 1.35,0.61 1.35,1.35v15.7c0,0.87 -0.35,1.69 -0.98,2.29l-7.89,7.58c-0.55,0.52 -1.41,0.52 -1.96,0l-7.91,-7.58c-0.62,-0.6 -0.98,-1.43 -0.97,-2.29l0.05,-15.7c0,-0.75 0.61,-1.36 1.36,-1.36zM355.46,188.03c6.86,0 12.42,5.56 12.42,12.42 0,6.86 -5.56,12.42 -12.42,12.42 -6.86,0 -12.42,-5.56 -12.42,-12.42 0,-6.86 5.56,-12.42 12.42,-12.42zM407.02,188.03c6.86,0 12.42,5.56 12.42,12.42 0,6.86 -5.56,12.42 -12.42,12.42 -6.86,0 -12.42,-5.56 -12.42,-12.42 0,-6.86 5.56,-12.42 12.42,-12.42zM29.38,190.15c0.87,-0 1.69,0.35 2.29,0.97l7.58,7.91c0.52,0.55 0.52,1.41 -0,1.96l-7.58,7.89c-0.6,0.62 -1.43,0.98 -2.29,0.98h-15.7c-0.75,0 -1.35,-0.61 -1.35,-1.35v-16.94c0,-0.75 0.61,-1.36 1.35,-1.36zM61.25,190.33h15.7c0.75,0 1.35,0.61 1.35,1.35v16.94c0,0.75 -0.61,1.36 -1.36,1.36l-15.7,0.05c-0.87,0 -1.7,-0.35 -2.29,-0.97l-7.58,-7.91c-0.52,-0.55 -0.52,-1.41 0,-1.96l7.58,-7.89c0.6,-0.62 1.43,-0.98 2.3,-0.98zM45.39,205.76c0.35,-0 0.71,0.13 0.98,0.39l7.91,7.58c0.62,0.6 0.98,1.43 0.98,2.29l-0.05,15.7c-0,0.75 -0.61,1.36 -1.36,1.36h-16.94c-0.75,0 -1.35,-0.61 -1.35,-1.35v-15.7c0,-0.87 0.35,-1.69 0.98,-2.29l7.89,-7.58c0.27,-0.26 0.63,-0.39 0.98,-0.39zM381.24,213.81c6.86,0 12.42,5.56 12.42,12.42 0,6.86 -5.56,12.42 -12.42,12.42 -6.86,0 -12.42,-5.56 -12.42,-12.42 0,-6.86 5.56,-12.42 12.42,-12.42zM48.04,243.19a14.41,14.41 0,0 1,14.41 14.41,14.41 14.41,0 0,1 -14.41,14.41 14.41,14.41 0,0 1,-14.41 -14.41,14.41 14.41,0 0,1 6.47,-11.69c0.3,-0.22 0.61,-0.43 0.93,-0.62a14.41,14.41 0,0 1,2.57 -1.21c1.17,-0.41 2.39,-0.66 3.65,-0.73a14.41,14.41 0,0 1,0.8 -0.16zM36.97,289.26h25.36l11.85,10.16h-25.36l-0.01,-0.01h-2.03l-4.9,-5 -4.9,-5h0.17z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="0.0999997"/>
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="50"
|
||||
android:viewportHeight="50">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19.313,4C19.012,4 18.707,3.988 18.406,4.188C18.105,4.387 18,4.699 18,5L18,41.594C18,41.992 18.289,42.395 18.688,42.594L26.688,45L27,45C27.199,45 27.395,44.914 27.594,44.813C27.895,44.613 28,44.301 28,44L28,13.406C28.602,13.707 29,14.301 29,15L29,26.094C29,26.395 29.199,26.805 29.5,26.906C29.699,27.008 31.199,27.906 34,27.906C36.699,27.906 40,26.414 40,19.313C40,13.613 36.813,9.293 31.313,7.594ZM17,26.406L5.906,30.406L4.313,31C1.613,32.102 0,33.887 0,35.688C0,39.488 2.699,41.688 7.5,41.688C10.102,41.688 13.301,41.113 17,39.813L17,36C16.102,36.301 15.113,36.699 14.313,37C12.711,37.602 11.5,37.813 10.5,37.813C9,37.813 8.301,37.301 8,37C7.602,36.699 7.398,36.313 7.5,35.813C7.602,34.813 8.801,33.895 11,33.094C11.5,32.895 14.898,31.699 17,31ZM36.5,28.906C34.102,29.008 31.602,29.395 29,30.094L29,34.688C30.102,34.289 31.586,33.801 33.688,33C38.488,31.301 40.492,31.488 41.094,31.688C42.293,31.789 42.801,32.5 43,33C43.5,34.5 41.613,35.188 38.813,36.188C37.512,36.688 31.898,38.688 29,39.688L29,44.313L44.5,38.813L45.688,38.313C47.688,37.613 50.199,36.301 50,34C49.898,31.801 47.211,30.695 45.313,30.094C42.512,29.195 39.5,28.805 36.5,28.906Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
||||
<gradient
|
||||
android:startColor="#FF00A7FF"
|
||||
android:endColor="#FF93278F"
|
||||
android:angle="270"/>
|
||||
</shape>
|
||||
@@ -1,10 +0,0 @@
|
||||
<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>
|
||||
@@ -1,8 +0,0 @@
|
||||
<vector android:height="24dp" android:viewportHeight="115.43"
|
||||
android:viewportWidth="115.43" android:width="24dp"
|
||||
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#000000" android:fillType="nonZero"
|
||||
android:pathData="m27.32,30.4c-15.13,0 -27.32,12.18 -27.32,27.32 0,15.13 12.18,27.32 27.32,27.32l60.79,0c15.13,0 27.32,-12.18 27.32,-27.32 0,-15.13 -12.18,-27.32 -27.32,-27.32zM24.62,38.44L90.61,38.44L90.61,76.22L24.62,76.22ZM10.54,44.52l4.03,0c0.18,0 0.32,0.14 0.32,0.32l0,3.74c0,0.21 -0.08,0.4 -0.23,0.55l-1.88,1.8c-0.13,0.12 -0.34,0.12 -0.47,0l-1.88,-1.8c-0.15,-0.14 -0.23,-0.34 -0.23,-0.55l0.01,-3.74c0,-0.18 0.15,-0.32 0.32,-0.32zM104.06,44.6c1.54,0 2.78,1.25 2.78,2.78 0,1.54 -1.25,2.78 -2.78,2.78 -1.54,0 -2.78,-1.25 -2.78,-2.78 0,-1.54 1.25,-2.78 2.78,-2.78zM109.1,49.65c1.54,0 2.78,1.25 2.78,2.78 0,1.54 -1.25,2.78 -2.78,2.78 -1.54,0 -2.78,-1.25 -2.78,-2.78 0,-1.54 1.25,-2.78 2.78,-2.78zM99.01,49.65c1.54,0 2.78,1.25 2.78,2.78 0,1.54 -1.25,2.78 -2.78,2.78 -1.54,0 -2.78,-1.25 -2.78,-2.78 0,-1.54 1.25,-2.78 2.78,-2.78zM8.78,50c0.21,-0 0.4,0.08 0.55,0.23l1.81,1.88c0.12,0.13 0.12,0.34 -0,0.47l-1.8,1.88c-0.14,0.15 -0.34,0.23 -0.55,0.23l-3.74,0c-0.18,0 -0.32,-0.14 -0.32,-0.32l0,-4.03c0,-0.18 0.14,-0.32 0.32,-0.32zM16.37,50.05l3.74,0c0.18,0 0.32,0.14 0.32,0.32l0,4.03c0,0.18 -0.14,0.32 -0.32,0.32l-3.74,0.01c-0.21,0 -0.4,-0.08 -0.55,-0.23l-1.8,-1.88c-0.12,-0.13 -0.12,-0.34 0,-0.47l1.8,-1.88c0.14,-0.15 0.34,-0.23 0.55,-0.23zM12.59,53.72c0.08,-0 0.17,0.03 0.23,0.09l1.88,1.81c0.15,0.14 0.23,0.34 0.23,0.55l-0.01,3.74c-0,0.18 -0.15,0.32 -0.32,0.32l-4.03,0c-0.18,0 -0.32,-0.14 -0.32,-0.32l0,-3.74c0,-0.21 0.08,-0.4 0.23,-0.55l1.88,-1.8c0.06,-0.06 0.15,-0.09 0.23,-0.09zM104.06,54.69c1.54,0 2.78,1.25 2.78,2.78 0,1.54 -1.25,2.78 -2.78,2.78 -1.54,0 -2.78,-1.25 -2.78,-2.78 0,-1.54 1.25,-2.78 2.78,-2.78zM15.83,66.39a2.87,2.87 0,0 1,2.87 2.87,2.87 2.87,0 0,1 -2.87,2.87 2.87,2.87 0,0 1,-2.87 -2.87,2.87 2.87,0 0,1 2.87,-2.87zM99.39,66.41a2.87,2.87 89.99,0 1,2.87 2.87,2.87 2.87,89.99 0,1 -2.87,2.87 2.87,2.87 89.99,0 1,-2.87 -2.87,2.87 2.87,89.99 0,1 2.87,-2.87z"
|
||||
android:strokeColor="#00000000" android:strokeLineCap="square"
|
||||
android:strokeLineJoin="round" android:strokeWidth="1"/>
|
||||
</vector>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false">
|
||||
|
||||
<include
|
||||
android:id="@+id/include"
|
||||
layout="@layout/appbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="56dp"
|
||||
android:background="@color/colorAmoled"
|
||||
android:backgroundTint="@color/colorAmoled"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/include"
|
||||
tools:context=".activities.MainActivity" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
This layout is used only on API 21 & 22 (Lollipop) because on these API levels,
|
||||
Android doesn't scale SVGs very well - we noticed the logo stretched across the
|
||||
screen and looked ugly.
|
||||
In SplashActivity, setContentView(R.layout.activity_splash) is called only for API 21 & 22.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/splashRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:srcCompat="@drawable/splash" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/toolbar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
app:liftOnScroll="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/Widget.MaterialComponents.Toolbar.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/colorAmoled"
|
||||
android:theme="@style/NPSTheme.ActionBar"
|
||||
app:layout_scrollFlags="scroll|enterAlways|snap" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
tools:context=".fragments.mainactivity.MainActivityFragment">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/appsRecycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="2"
|
||||
tools:listitem="@layout/recycler_item_grid" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/tsvChooserButton"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:backgroundTint="@color/colorPrimary"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_add_24dp"
|
||||
app:borderWidth="2dp"
|
||||
app:fabCustomSize="52dp"
|
||||
app:layout_anchor="@+id/appsRecycler"
|
||||
app:layout_anchorGravity="bottom|center"
|
||||
app:maxImageSize="32dp" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="0dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="214dp"
|
||||
android:backgroundTint="@color/colorAmoled"
|
||||
app:cardBackgroundColor="@color/colorPrimary"
|
||||
app:cardElevation="4dp"
|
||||
app:cardUseCompatPadding="false"
|
||||
app:contentPadding="15dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView2"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_games_placeholder_24dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:ellipsize="marquee"
|
||||
android:enabled="true"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:scrollHorizontally="true"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textColor="@color/colorOnPrimary"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView2"
|
||||
tools:text="App Title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appRegion"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/rect_light_blue"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:paddingRight="10dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="ASIA" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appMinFW"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/rect_red"
|
||||
android:padding="4dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/appRegion"
|
||||
tools:text="3.69" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appLastDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:padding="4dp"
|
||||
android:textColor="@color/colorOnSecondary"
|
||||
android:textSize="10sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:text="2018-04-22" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -0,0 +1,105 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="0dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:backgroundTint="@color/colorAmoled"
|
||||
app:cardBackgroundColor="@color/colorPrimary"
|
||||
app:cardElevation="4dp"
|
||||
app:cardUseCompatPadding="false"
|
||||
app:contentPadding="15dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="62dp"
|
||||
android:layout_height="56dp"
|
||||
android:contentDescription="app icon"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_games_placeholder_24dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:ellipsize="marquee"
|
||||
android:focusable="auto"
|
||||
android:focusableInTouchMode="true"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:scrollHorizontally="true"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textColor="@color/colorOnPrimary"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/imageView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="App Title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appRegion"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:background="@drawable/rect_light_blue"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:paddingRight="10dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/appMinFW"
|
||||
app:layout_constraintStart_toEndOf="@+id/imageView"
|
||||
tools:text="ASIA" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appMinFW"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/rect_red"
|
||||
android:padding="4dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/appRegion"
|
||||
tools:text="3.69" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appLastDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="4dp"
|
||||
android:textColor="@color/colorOnSecondary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:text="2020-02-12" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/prefers"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/prefs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -13,7 +13,7 @@
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/colorError"
|
||||
android:textColor="@color/colorAccent"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
@@ -70,6 +70,19 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/singleAppTitle" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/singleAppCopyPKGButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="66dp"
|
||||
android:layout_marginStart="48dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
android:text="@string/copy_pkg"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/singleAppDownloadButton" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/singleAppCopyZRIFButton"
|
||||
android:layout_width="0dp"
|
||||
@@ -81,7 +94,7 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/singleAppDownloadButton" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/singleAppCopyPKGButton" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/singleAppContentIdTitle"
|
||||
@@ -134,6 +147,23 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/singleAppContentId" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/singleAppVerTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="48dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:text="@string/version"
|
||||
android:textAlignment="center"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/singleAppSize" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/singleAppFwVerTitle"
|
||||
android:layout_width="match_parent"
|
||||
@@ -168,6 +198,23 @@
|
||||
app:layout_constraintTop_toBottomOf="@+id/singleAppSizeTitle"
|
||||
tools:text="Size" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/singleAppVer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="48dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:textAlignment="center"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/singleAppVerTitle"
|
||||
tools:text="Version" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/singleAppFwVer"
|
||||
android:layout_width="match_parent"
|
||||
@@ -185,7 +232,6 @@
|
||||
app:layout_constraintTop_toBottomOf="@+id/singleAppFwVerTitle"
|
||||
tools:text="MinFW" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_search"
|
||||
android:icon="@drawable/ic_search_24dp"
|
||||
android:title="@string/search"
|
||||
app:iconTint="?attr/colorOnPrimary"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:icon="@drawable/ic_settings"
|
||||
android:title="@string/title_activity_settings"
|
||||
app:iconTint="?attr/colorOnPrimary"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
@@ -7,5 +7,5 @@
|
||||
<color name="colorSecondaryDark">#515151</color>
|
||||
<color name="colorOnSecondary">#DFFFFFFF</color>
|
||||
<color name="colorAmoled">#000000</color>
|
||||
<color name="colorError">#F93422</color>
|
||||
<color name="colorAccent">#F93422</color>
|
||||
</resources>
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
<resources>
|
||||
<string name="app_name">NPS Browser</string>
|
||||
<string name="title_activity_settings">设置</string>
|
||||
<string name="search">搜索…</string>
|
||||
<string name="search_tooltip">搜索</string>
|
||||
<string name="search">搜索</string>
|
||||
<string name="download">下载</string>
|
||||
<string name="copy_zrif">复制zRIF</string>
|
||||
<string name="copy_pkg">复制PKG地址</string>
|
||||
<string name="copy_zrif">复制zRIF密钥</string>
|
||||
<string name="content_id">内容ID</string>
|
||||
<string name="size">大小</string>
|
||||
<string name="version">版本</string>
|
||||
<string name="minfw">最低固件版本</string>
|
||||
<string name="single_app_icon">应用图标</string>
|
||||
<string name="no_license">注意:这个内容没有提供授权</string>
|
||||
@@ -16,7 +17,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,12 +2,13 @@
|
||||
<resources>
|
||||
<string name="app_name">NPS Browser</string>
|
||||
<string name="title_activity_settings">設置</string>
|
||||
<string name="search">搜尋…</string>
|
||||
<string name="search_tooltip">搜尋</string>
|
||||
<string name="search">搜尋</string>
|
||||
<string name="download">下載</string>
|
||||
<string name="copy_zrif">複製zRIF</string>
|
||||
<string name="copy_pkg">複製PKG地址</string>
|
||||
<string name="copy_zrif">複製zRIF金鑰</string>
|
||||
<string name="content_id">內容ID</string>
|
||||
<string name="size">檔案大小</string>
|
||||
<string name="version">版本</string>
|
||||
<string name="minfw">最低韌體版本</string>
|
||||
<string name="single_app_icon">應用程式圖標</string>
|
||||
<string name="no_license">注意:這個內容沒有提供授權</string>
|
||||
@@ -16,7 +17,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>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?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>
|
||||
@@ -7,6 +7,5 @@
|
||||
<color name="colorSecondaryDark">#888888</color>
|
||||
<color name="colorOnSecondary">#111111</color>
|
||||
<color name="colorAmoled">#FFFFFF</color>
|
||||
<color name="colorError">#F93422</color>
|
||||
<color name="colorAccent">#545FBF</color>
|
||||
<color name="colorAccent">#F93422</color>
|
||||
</resources>
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
<resources>
|
||||
<string name="app_name">NPS Browser</string>
|
||||
<string name="title_activity_settings">Settings</string>
|
||||
<string name="search">Search…</string>
|
||||
<string name="search_tooltip">Search</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="copy_zrif">Copy zRIF</string>
|
||||
<string name="copy_pkg">Copy PKG URL</string>
|
||||
<string name="copy_zrif">Copy zRIF Key</string>
|
||||
<string name="content_id">Content ID</string>
|
||||
<string name="size">Size</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="minfw">Minimum Firmware Version</string>
|
||||
<string name="single_app_icon">Single app icon</string>
|
||||
<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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="NPSTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<style name="NPSTheme" parent="Theme.MaterialComponents.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.Material3.DayNight.Dialog.Alert">
|
||||
<style name="NPSTheme.AlertDialog" parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
|
||||
<item name="colorPrimary">@color/colorOnPrimary</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
@@ -30,13 +30,13 @@
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="NPSTheme.Splash" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/colorAccent</item>
|
||||
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_nps_fg_no_shadow</item>
|
||||
<item name="windowSplashScreenAnimationDuration">800</item>
|
||||
|
||||
<item name="postSplashScreenTheme">@style/NPSTheme</item>
|
||||
<style name="NPSTheme.Splash" parent="NPSTheme">
|
||||
<item name="android:statusBarColor">#FF00A7FF</item>
|
||||
<item name="android:windowBackground">@drawable/splash</item>
|
||||
</style>
|
||||
|
||||
<style name="NPSTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<style name="NPSTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<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>
|
||||
@@ -1,15 +1,17 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
val kotlin_version = "1.8.22"
|
||||
|
||||
ext {
|
||||
kotlin_version = '1.8.10'
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:8.0.2")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||
classpath 'com.android.tools.build:gradle:7.4.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
@@ -19,13 +21,11 @@ buildscript {
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven( url = "https://jitpack.io" )
|
||||
jcenter()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
register("clean", Delete::class) {
|
||||
delete(rootProject.buildDir)
|
||||
}
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
+2
-3
@@ -15,8 +15,7 @@ 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
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
#Wed Jun 21 06:42:34 EEST 2023
|
||||
#Tue Oct 20 12:27:05 GMT+03:00 2020
|
||||
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-7.6-all.zip
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
rootProject.name='NPS Browser'
|
||||
include ':app'
|
||||
@@ -1,2 +0,0 @@
|
||||
rootProject.name="NPS Browser"
|
||||
include("app")
|
||||
Reference in New Issue
Block a user