WIP: UI for package list and grid + start of handling data

This commit is contained in:
2023-07-01 07:59:50 +03:00
parent b6219355bd
commit 6cf6cd6f08
24 changed files with 429 additions and 436 deletions
+3 -1
View File
@@ -100,7 +100,7 @@ dependencies {
// Compose extras library for controlling system ui and etc. // Compose extras library for controlling system ui and etc.
implementation("com.google.accompanist:accompanist-systemuicontroller:0.31.2-alpha") implementation("com.google.accompanist:accompanist-systemuicontroller:0.31.2-alpha")
implementation("com.github.doyaaaaaken:kotlin-csv-jvm:1.8.0") implementation("com.github.doyaaaaaken:kotlin-csv-jvm:1.9.1")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
@@ -110,4 +110,6 @@ dependencies {
debugImplementation("androidx.compose.ui:ui-test-manifest") debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.navigation:navigation-compose:2.6.0") 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")
} }
@@ -8,6 +8,7 @@ import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
enum class ConsoleType { enum class ConsoleType {
@@ -24,6 +25,11 @@ enum class DataType {
THEMES, THEMES,
} }
enum class Layout {
LIST,
GRID,
}
class Preferences(private val context: Context) { class Preferences(private val context: Context) {
companion object { companion object {
private val Context.dataStore by preferencesDataStore("settings") private val Context.dataStore by preferencesDataStore("settings")
@@ -53,7 +59,7 @@ class Preferences(private val context: Context) {
} }
val layout = context.dataStore.data.map{ prefs -> val layout = context.dataStore.data.map{ prefs ->
prefs[DATA_LAYOUT] ?: 0 prefs[DATA_LAYOUT] ?: Layout.LIST.ordinal
} }
val seenOnboarding = context.dataStore.data.map{ prefs -> val seenOnboarding = context.dataStore.data.map{ prefs ->
@@ -180,7 +186,20 @@ class Preferences(private val context: Context) {
} }
} }
} }
}
suspend fun getAvailableTsvs(): List<Pair<ConsoleType, DataType>> {
val list = mutableListOf<Pair<ConsoleType, DataType>>()
if (psvGames.first().isNotEmpty()) list.add(Pair(ConsoleType.PSVITA, DataType.GAMES))
if (psvDLC.first().isNotEmpty()) list.add(Pair(ConsoleType.PSVITA, DataType.DLC))
if (psvThemes.first().isNotEmpty()) list.add(Pair(ConsoleType.PSVITA, DataType.THEMES))
if (ps3Games.first().isNotEmpty()) list.add(Pair(ConsoleType.PS3, DataType.GAMES))
if (ps3DLC.first().isNotEmpty()) list.add(Pair(ConsoleType.PS3, DataType.DLC))
if (pspGames.first().isNotEmpty()) list.add(Pair(ConsoleType.PSP, DataType.GAMES))
if (pspDLC.first().isNotEmpty()) list.add(Pair(ConsoleType.PSP, DataType.DLC))
if (psxGames.first().isNotEmpty()) list.add(Pair(ConsoleType.PSX, DataType.GAMES))
if (psmGames.first().isNotEmpty()) list.add(Pair(ConsoleType.PSM, DataType.GAMES))
return list
} }
suspend fun setHMACKey(key: String) { suspend fun setHMACKey(key: String) {
@@ -3,17 +3,29 @@ package com.illusionware.npsbrowser.activities
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width 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.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
@@ -23,6 +35,9 @@ import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme 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.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -40,18 +55,32 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color 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.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource 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.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.illusionware.npsbrowser.ConsoleType
import com.illusionware.npsbrowser.Layout
import com.illusionware.npsbrowser.Preferences import com.illusionware.npsbrowser.Preferences
import com.illusionware.npsbrowser.R import com.illusionware.npsbrowser.R
import com.illusionware.npsbrowser.ui.theme.ColorAccent import com.illusionware.npsbrowser.ui.theme.ColorAccent
@@ -60,12 +89,18 @@ import com.illusionware.npsbrowser.ui.theme.Typography
import com.illusionware.npsbrowser.ui.components.* import com.illusionware.npsbrowser.ui.components.*
import com.illusionware.npsbrowser.ui.pages.SettingsScreen import com.illusionware.npsbrowser.ui.pages.SettingsScreen
import com.illusionware.npsbrowser.ui.pages.OnBoarding import com.illusionware.npsbrowser.ui.pages.OnBoarding
import com.illusionware.npsbrowser.ui.theme.ColorOnPrimaryLight
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
val PlaceholderColor = Color(0x1F888888)
val redHatDisplayFamily = FontFamily(
Font(R.font.red_hat_display_bold, FontWeight.Bold),
)
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -128,13 +163,18 @@ fun Router() {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@Preview() @Preview
fun HomePage(navigateToSettings: () -> Unit = {}) { fun HomePage(navigateToSettings: () -> Unit = {}) {
val prefs = Preferences(LocalContext.current)
var isSearchOpen by rememberSaveable { mutableStateOf(false)} var isSearchOpen by rememberSaveable { mutableStateOf(false)}
var searchQuery by rememberSaveable { mutableStateOf("")} var searchQuery by rememberSaveable { mutableStateOf("")}
val searchBar = remember { FocusRequester() } val searchBar = remember { FocusRequester() }
var layoutDialogOpen by remember { mutableStateOf(false) } var layoutDialogOpen by remember { mutableStateOf(false) }
val tsvs by remember { runBlocking { mutableStateOf(prefs.getAvailableTsvs()) }}
var selectedBarItem by remember { mutableStateOf(ConsoleType.PSVITA) }
// val noTsvs by remember { mutableStateOf(false) }
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -213,12 +253,27 @@ fun HomePage(navigateToSettings: () -> Unit = {}) {
} }
} }
) )
},
bottomBar = {
if (tsvs.isNotEmpty()) {
NavBar(
selectedTab = selectedBarItem,
onTabSelected = { selectedBarItem = it },
)
}
} }
) { padding -> ) { padding ->
Column(Modifier.padding(padding)) { Column(Modifier.padding(padding)) {
NoTsvs(navigateToSettings = navigateToSettings) if (tsvs.isEmpty()) {
NoTsvs(navigateToSettings = navigateToSettings)
} else {
// when (selectedBarItem) {
// ConsoleType.PSVITA -> VitaPage(searchQuery = searchQuery)
// ConsoleType.PS3 -> PS3Page(searchQuery = searchQuery)
// }
PackageList(selectedBarItem)
}
} }
// TODO: only show NoTsvs when there are no Tsvs loaded
if (layoutDialogOpen) { if (layoutDialogOpen) {
LayoutDialog(onDismiss = { layoutDialogOpen = false }) LayoutDialog(onDismiss = { layoutDialogOpen = false })
@@ -226,6 +281,52 @@ fun HomePage(navigateToSettings: () -> Unit = {}) {
} }
} }
@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 @Composable
@Preview @Preview
fun NoTsvs(padding: PaddingValues = PaddingValues(0.dp), navigateToSettings: () -> Unit = {}) { fun NoTsvs(padding: PaddingValues = PaddingValues(0.dp), navigateToSettings: () -> Unit = {}) {
@@ -267,9 +368,229 @@ fun NoTsvs(padding: PaddingValues = PaddingValues(0.dp), navigateToSettings: ()
} }
@Composable @Composable
fun LayoutDialog(onDismiss: () -> Unit = {}) { @Preview
fun PackageList(type: ConsoleType = ConsoleType.PSVITA) {
val prefs = Preferences(LocalContext.current) val prefs = Preferences(LocalContext.current)
val layout = prefs.layout.collectAsState(initial = "") val layout = prefs.layout.collectAsState(initial = Layout.LIST.ordinal).value
// val thing = when (type) {
// ConsoleType.PSVITA -> {
// var list = ArrayList<Package>()
// list.add(Package("test"))
// val gs = prefs.psvGames.collectAsState(initial = emptyList<Package>())
// list.add(gs.value)
// }
// ConsoleType.PS3 -> {
// prefs.ps3Games.collectAsState(initial = emptyList())
// }
// ConsoleType.PSP -> {
// prefs.pspGames.collectAsState(initial = emptyList())
// }
// ConsoleType.PSX -> {
// prefs.psxGames.collectAsState(initial = emptyList())
// }
// ConsoleType.PSM -> {
// prefs.psmGames.collectAsState(initial = emptyList())
// }
// }
Column(Modifier.fillMaxSize()) {
if (layout == Layout.LIST.ordinal) {
LazyColumn {
items(15) {
PackageListItem()
}
}
} else {
LazyVerticalGrid(
columns = GridCells.Adaptive(140.dp),
contentPadding = PaddingValues(8.dp),
) {
items(15) {
PackageGridItem()
}
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PackageListItem() {
Box(
Modifier
.fillMaxWidth()
.clickable { /* TODO: */ }) {
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("https://picsum.photos/200")
.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 = "10 Second Ninja X",
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 = "US")
Text(text = "3.69")
Text(text = "223.8MB")
Tag(title = "GAME", small = true)
}
}
}
}
Column {
Icon(
painter = painterResource(id = R.drawable.no_url_icon),
contentDescription = "No PKG URL",
tint = Color(0xFFCB5A5A)
)
Icon(
painter = painterResource(id = R.drawable.no_key_icon),
contentDescription = "No Zrif 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
@Preview
fun PackageGridItem() {
Box(
Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.clickable { /* TODO: */ }
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.Start,
modifier = Modifier.padding(8.dp)
) {
Layout(
content = {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data("https://picsum.photos/200")
.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 = "GAME")
}
) { 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 = "10 Second Ninja X",
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 = "US")
Text(text = "3.69")
Text(text = "223.8MB")
}
}
Row {
Icon(
painter = painterResource(id = R.drawable.no_url_icon),
contentDescription = "No PKG URL",
tint = Color(0xFFCB5A5A)
)
Icon(
painter = painterResource(id = R.drawable.no_key_icon),
contentDescription = "No Zrif Key",
tint = Color(0xFFCB5A5A)
)
}
}
}
}
@Composable
fun LayoutDialog(onDismiss: () -> Unit) {
val prefs = Preferences(LocalContext.current)
val layout = prefs.layout.collectAsState(initial = Layout.LIST.ordinal).value
NPSAlertDialog( NPSAlertDialog(
onDismiss = onDismiss, onDismiss = onDismiss,
@@ -283,13 +604,13 @@ fun LayoutDialog(onDismiss: () -> Unit = {}) {
Column { Column {
NPSRadioButton( NPSRadioButton(
text = "List", text = "List",
selected = layout.value == 0, selected = layout == Layout.LIST.ordinal,
onClick = { CoroutineScope(Dispatchers.IO).launch { prefs.changeLayout(0) } }, onClick = { CoroutineScope(Dispatchers.IO).launch { prefs.changeLayout(Layout.LIST.ordinal) } },
) )
NPSRadioButton( NPSRadioButton(
text = "Grid", text = "Grid",
selected = layout.value == 1, selected = layout == Layout.GRID.ordinal,
onClick = { CoroutineScope(Dispatchers.IO).launch { prefs.changeLayout(1) } }, onClick = { CoroutineScope(Dispatchers.IO).launch { prefs.changeLayout(Layout.GRID.ordinal) } },
) )
} }
} }
@@ -93,7 +93,10 @@ const val PAGE_COUNT = 3
@Composable @Composable
fun OnBoarding(navigateToHome: () -> Unit = {}, darkTheme: Boolean = false) { fun OnBoarding(navigateToHome: () -> Unit = {}, darkTheme: Boolean = false) {
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
val pagerState = rememberPagerState() val pagerState = rememberPagerState(
initialPage = 0,
initialPageOffsetFraction = 0f
)
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val context = LocalContext.current val context = LocalContext.current
@@ -0,0 +1,45 @@
package com.illusionware.npsbrowser.viewmodels
import androidx.lifecycle.ViewModel
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.ConsoleType
import com.illusionware.npsbrowser.DataType
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
data class Package(
val id: String,
val title: String,
val region: String,
val minFW: String,
val pkgUrl: String,
val pkgSize: Int,
val consoleType: ConsoleType,
val dataType: DataType,
)
data class PackageListUiState(
val packages: List<Package> = emptyList(),
val error: String = "",
)
class PackageListViewModel(): ViewModel() {
// TODO: actually properly think about and implement this
private val _uiState = MutableStateFlow(PackageListUiState())
val uiState: StateFlow<PackageListUiState> = _uiState.asStateFlow()
private val tsvReader = csvReader {
charset = "ISO_8859_1"
quoteChar = '"'
delimiter = '\t'
excessFieldsRowBehaviour = ExcessFieldsRowBehaviour.TRIM
insufficientFieldsRowBehaviour = InsufficientFieldsRowBehaviour.EMPTY_STRING
}
fun loadPackagesFromTsv() {
val file = "psv_games.tsv"
val rows = tsvReader.readAll(file)
}
}
@@ -1,6 +0,0 @@
<?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>
@@ -1,6 +0,0 @@
<?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>
-15
View File
@@ -1,15 +0,0 @@
<?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.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -1,5 +0,0 @@
<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>
@@ -1,9 +0,0 @@
<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>
@@ -1,9 +0,0 @@
<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>
+11
View File
@@ -0,0 +1,11 @@
<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>
+15
View File
@@ -0,0 +1,15 @@
<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,7 +0,0 @@
<?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>
Binary file not shown.
-31
View File
@@ -1,31 +0,0 @@
<?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>
@@ -1,19 +0,0 @@
<?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>
-18
View File
@@ -1,18 +0,0 @@
<?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>
-39
View File
@@ -1,39 +0,0 @@
<?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>
@@ -1,116 +0,0 @@
<?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>
@@ -1,105 +0,0 @@
<?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>
@@ -1,21 +0,0 @@
<?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>
-17
View File
@@ -1,17 +0,0 @@
<?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>