From a99be170c0d19b26bdc500e658a37cf1e0169fff Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Sat, 24 Jun 2023 06:47:18 +0300 Subject: [PATCH] refactor/feat/WIP: completely move to compose, partially finished onboarding screen --- app/build.gradle.kts | 40 +- .../com/illusionware/npsbrowser/AppData.kt | 59 --- .../illusionware/npsbrowser/Preferences.kt | 47 ++ .../npsbrowser/RecyclerTouchListener.kt | 44 -- .../npsbrowser/ThemePreference.kt | 34 -- .../npsbrowser/activities/BaseActivity.kt | 40 -- .../npsbrowser/activities/MainActivity.kt | 326 ++++++++++-- .../npsbrowser/adapters/AppAdapter.kt | 86 ---- .../npsbrowser/fragments/SettingsFragment.kt | 42 -- .../npsbrowser/fragments/SingleAppFragment.kt | 191 ------- .../mainactivity/MainActivityFragment.kt | 283 ----------- .../mainactivity/MainActivityViewModel.kt | 7 - .../ui/components/NPSAlertDialog.kt | 51 ++ .../npsbrowser/ui/components/NPSIconButton.kt | 20 + .../ui/components/NPSRadioButton.kt | 23 + .../npsbrowser/ui/pages/Onboarding.kt | 474 ++++++++++++++++++ .../npsbrowser/ui/pages/Settings.kt | 258 ++++++++++ .../illusionware/npsbrowser/ui/theme/Color.kt | 90 ++++ .../illusionware/npsbrowser/ui/theme/Theme.kt | 122 +++++ .../npsbrowser/ui/theme/Typography.kt | 34 ++ app/src/main/res/drawable/lock_open.xml | 9 + .../main/res/drawable/nps_fg_onboarding.xml | 118 +++++ app/src/main/res/drawable/ph_info.xml | 9 + app/src/main/res/drawable/tsv_file.xml | 10 + app/src/main/res/values-zh-rCN/strings.xml | 5 +- app/src/main/res/values-zh-rTW/strings.xml | 5 +- app/src/main/res/values/arrays.xml | 12 - app/src/main/res/values/strings.xml | 6 +- app/src/main/res/values/styles.xml | 10 +- app/src/main/res/xml/root_preferences.xml | 19 - build.gradle.kts | 2 - gradle.properties | 2 - gradle/wrapper/gradle-wrapper.properties | 4 +- 33 files changed, 1574 insertions(+), 908 deletions(-) delete mode 100644 app/src/main/java/com/illusionware/npsbrowser/AppData.kt create mode 100644 app/src/main/java/com/illusionware/npsbrowser/Preferences.kt delete mode 100644 app/src/main/java/com/illusionware/npsbrowser/RecyclerTouchListener.kt delete mode 100644 app/src/main/java/com/illusionware/npsbrowser/ThemePreference.kt delete mode 100644 app/src/main/java/com/illusionware/npsbrowser/activities/BaseActivity.kt delete mode 100644 app/src/main/java/com/illusionware/npsbrowser/adapters/AppAdapter.kt delete mode 100644 app/src/main/java/com/illusionware/npsbrowser/fragments/SettingsFragment.kt delete mode 100644 app/src/main/java/com/illusionware/npsbrowser/fragments/SingleAppFragment.kt delete mode 100644 app/src/main/java/com/illusionware/npsbrowser/fragments/mainactivity/MainActivityFragment.kt delete mode 100644 app/src/main/java/com/illusionware/npsbrowser/fragments/mainactivity/MainActivityViewModel.kt create mode 100644 app/src/main/java/com/illusionware/npsbrowser/ui/components/NPSAlertDialog.kt create mode 100644 app/src/main/java/com/illusionware/npsbrowser/ui/components/NPSIconButton.kt create mode 100644 app/src/main/java/com/illusionware/npsbrowser/ui/components/NPSRadioButton.kt create mode 100644 app/src/main/java/com/illusionware/npsbrowser/ui/pages/Onboarding.kt create mode 100644 app/src/main/java/com/illusionware/npsbrowser/ui/pages/Settings.kt create mode 100644 app/src/main/java/com/illusionware/npsbrowser/ui/theme/Color.kt create mode 100644 app/src/main/java/com/illusionware/npsbrowser/ui/theme/Theme.kt create mode 100644 app/src/main/java/com/illusionware/npsbrowser/ui/theme/Typography.kt create mode 100644 app/src/main/res/drawable/lock_open.xml create mode 100644 app/src/main/res/drawable/nps_fg_onboarding.xml create mode 100644 app/src/main/res/drawable/ph_info.xml create mode 100644 app/src/main/res/drawable/tsv_file.xml delete mode 100644 app/src/main/res/values/arrays.xml delete mode 100644 app/src/main/res/xml/root_preferences.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8e03af1..ac22ae4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,8 +4,6 @@ import java.io.FileInputStream plugins { id("com.android.application") kotlin("android") - id("kotlin-parcelize") - id("kotlin-kapt") } val keystoreProperties = Properties() @@ -32,7 +30,6 @@ android { } buildFeatures { - viewBinding = true compose = true aidl = false @@ -52,7 +49,8 @@ android { buildTypes { named("release") { signingConfig = signingConfigs.getByName("release") - isMinifyEnabled = false + isMinifyEnabled = true + isShrinkResources = true proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro") } } @@ -69,12 +67,6 @@ android { kotlinOptions { jvmTarget = "17" } - packagingOptions { - resources { - excludes += "/META-INF/{AL2.0,LGPL2.1}" - } - } - } repositories { @@ -83,8 +75,8 @@ repositories { } dependencies { - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1") - implementation("androidx.activity:activity-compose:1.5.1") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") + implementation("androidx.activity:activity-compose:1.7.2") implementation(platform("androidx.compose:compose-bom:2022.10.00")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") @@ -96,32 +88,26 @@ dependencies { androidTestImplementation(composeBom) // Material 3 - implementation("androidx.compose.material3:material3") + implementation("androidx.compose.material3:material3:1.1.0") + implementation("androidx.compose.material:material-icons-extended:1.4.3") // Android Studio Preview support implementation("androidx.compose.ui:ui-tooling-preview") debugImplementation("androidx.compose.ui:ui-tooling") - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("androidx.core:core-ktx:1.10.1") - implementation("androidx.constraintlayout:constraintlayout:2.1.4") - implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0") + // Preferences DataStore (alternative to SharedPreferences) + implementation("androidx.datastore:datastore-preferences:1.0.0") + + // Compose extras library for controlling system ui and etc. + implementation("com.google.accompanist:accompanist-systemuicontroller:0.31.2-alpha") + implementation("com.github.doyaaaaaken:kotlin-csv-jvm:1.8.0") - implementation("com.github.wrdlbrnft:sorted-list-adapter:0.3.0.27") - implementation("androidx.cardview:cardview:1.0.0") - implementation("androidx.navigation:navigation-fragment-ktx:2.6.0") - implementation("androidx.navigation:navigation-ui-ktx:2.6.0") - implementation("androidx.preference:preference-ktx:1.2.0") - implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") - implementation("androidx.legacy:legacy-support-v4:1.0.0") - implementation("androidx.recyclerview:recyclerview:1.3.0") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") implementation("com.google.android.material:material:1.9.0") implementation("com.github.bumptech.glide:glide:4.14.2") annotationProcessor("com.github.bumptech.glide:compiler:4.14.2") - implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") debugImplementation("androidx.compose.ui:ui-test-manifest") implementation("androidx.core:core-splashscreen:1.0.1") + implementation("androidx.navigation:navigation-compose:2.6.0") } diff --git a/app/src/main/java/com/illusionware/npsbrowser/AppData.kt b/app/src/main/java/com/illusionware/npsbrowser/AppData.kt deleted file mode 100644 index a195d6f..0000000 --- a/app/src/main/java/com/illusionware/npsbrowser/AppData.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.illusionware.npsbrowser - -import com.github.wrdlbrnft.sortedlistadapter.SortedListAdapter - -data class AppData( - val titleID: String?, val region: String?, val title: String?, val link: String?, - val license: String?, val contentID: String?, val lastDateTime: String?, - val fileSize: Number?, val sha256: String?, val minFW: String? -) : SortedListAdapter.ViewModel { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AppData - - if (titleID != other.titleID) return false - if (region != other.region) return false - if (title != other.title) return false - if (link != other.link) return false - if (license != other.license) return false - if (contentID != other.contentID) return false - if (lastDateTime != other.lastDateTime) return false - if (fileSize != other.fileSize) return false - if (sha256 != other.sha256) return false - if (minFW != other.minFW) return false - - return true - } - - override fun hashCode(): Int { - var result = titleID?.hashCode() ?: 0 - result = 31 * result + (region?.hashCode() ?: 0) - result = 31 * result + (title?.hashCode() ?: 0) - result = 31 * result + (link?.hashCode() ?: 0) - result = 31 * result + (license?.hashCode() ?: 0) - result = 31 * result + (contentID?.hashCode() ?: 0) - result = 31 * result + (lastDateTime?.hashCode() ?: 0) - result = 31 * result + (fileSize?.hashCode() ?: 0) - result = 31 * result + (sha256?.hashCode() ?: 0) - result = 31 * result + (minFW?.hashCode() ?: 0) - return result - } - - override fun isSameModelAs(model: T): Boolean { - if (model is AppData) { - val other: AppData = model - return "${other.titleID} ${other.title}" == "$titleID $title" - } - return false - } - - override fun isContentTheSameAs(model: T): Boolean { - if (model is AppData) { - val other: AppData = model - return title?.equals(other.title) ?: (other.title == null) - } - return false - } -} \ No newline at end of file diff --git a/app/src/main/java/com/illusionware/npsbrowser/Preferences.kt b/app/src/main/java/com/illusionware/npsbrowser/Preferences.kt new file mode 100644 index 0000000..c4b93da --- /dev/null +++ b/app/src/main/java/com/illusionware/npsbrowser/Preferences.kt @@ -0,0 +1,47 @@ +package com.illusionware.npsbrowser + +import android.content.Context +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.map + +class Preferences(private val context: Context) { + companion object { + private val Context.dataStore by preferencesDataStore(name = "settings") + private val APP_THEME = intPreferencesKey("app_theme") + private val DATA_LAYOUT = intPreferencesKey("layout") + private val SEEN_ONBOARDING = booleanPreferencesKey("seen_onboarding") + } + + val theme = context.dataStore.data.map { prefs -> + prefs[APP_THEME] ?: 2 + } + + val layout = context.dataStore.data.map{ prefs -> + prefs[DATA_LAYOUT] ?: 0 + } + + val seenOnboarding = context.dataStore.data.map{ prefs -> + prefs[SEEN_ONBOARDING] ?: false + } + + suspend fun changeTheme(theme: Int) { + context.dataStore.edit { prefs -> + prefs[APP_THEME] = theme + } + } + + suspend fun changeLayout(layout: Int) { + context.dataStore.edit { prefs -> + prefs[DATA_LAYOUT] = layout + } + } + + suspend fun finishOnboarding() { + context.dataStore.edit { prefs -> + prefs[SEEN_ONBOARDING] = true + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/illusionware/npsbrowser/RecyclerTouchListener.kt b/app/src/main/java/com/illusionware/npsbrowser/RecyclerTouchListener.kt deleted file mode 100644 index af68159..0000000 --- a/app/src/main/java/com/illusionware/npsbrowser/RecyclerTouchListener.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.illusionware.npsbrowser - -import android.content.Context -import android.view.GestureDetector -import android.view.GestureDetector.SimpleOnGestureListener -import android.view.MotionEvent -import android.view.View -import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener - -class RecyclerTouchListener(context: Context?, recyclerView: RecyclerView, - private val clickListener: ClickListener?) : OnItemTouchListener { - interface ClickListener { - fun onClick(view: View?, position: Int) - fun onLongClick(view: View?, recyclerView: RecyclerView?, position: Int) - } - - private val gestureDetector: GestureDetector - override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean { - val child = rv.findChildViewUnder(e.x, e.y) - if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) { - clickListener.onClick(child, rv.getChildAdapterPosition(child)) - } - return false - } - - override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {} - override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {} - - init { - gestureDetector = GestureDetector(context, object : SimpleOnGestureListener() { - override fun onSingleTapUp(e: MotionEvent): Boolean { - return true - } - - override fun onLongPress(e: MotionEvent) { - val child = recyclerView.findChildViewUnder(e.x, e.y) - if (child != null && clickListener != null) { - clickListener.onLongClick(child, recyclerView, recyclerView.getChildAdapterPosition(child)) - } - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/illusionware/npsbrowser/ThemePreference.kt b/app/src/main/java/com/illusionware/npsbrowser/ThemePreference.kt deleted file mode 100644 index 883ed94..0000000 --- a/app/src/main/java/com/illusionware/npsbrowser/ThemePreference.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.illusionware.npsbrowser - -import android.content.Context -import android.util.AttributeSet -import androidx.appcompat.app.AppCompatDelegate -import androidx.preference.ListPreference - -class ThemePreference : ListPreference { - constructor(context : Context?, attrs : AttributeSet?, defStyleAttr : Int) : super(context!!, attrs, defStyleAttr) - - constructor(context : Context?, attrs : AttributeSet?) : super(context!!, attrs) - - constructor(context : Context?) : super(context!!) - - override fun callChangeListener(newValue : Any?) : Boolean { - AppCompatDelegate.setDefaultNightMode(when ((newValue as String).toInt()) { - 0 -> AppCompatDelegate.MODE_NIGHT_NO - 1 -> AppCompatDelegate.MODE_NIGHT_YES - 2 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED - }) - - return super.callChangeListener(newValue) - } - - override fun setValue(value: String?) { - super.setValue(value) - summary = value - } - - override fun setSummary(summary: CharSequence?) { - super.setSummary(entry) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/illusionware/npsbrowser/activities/BaseActivity.kt b/app/src/main/java/com/illusionware/npsbrowser/activities/BaseActivity.kt deleted file mode 100644 index e31284f..0000000 --- a/app/src/main/java/com/illusionware/npsbrowser/activities/BaseActivity.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.illusionware.npsbrowser.activities - -import android.content.SharedPreferences -import android.content.res.Configuration -import android.os.Bundle -import android.view.View -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.app.AppCompatDelegate -import androidx.preference.PreferenceManager -import com.illusionware.npsbrowser.R - -open class BaseActivity : AppCompatActivity() { - - private lateinit var sharedPreferences : SharedPreferences - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - PreferenceManager.setDefaultValues(this, R.xml.root_preferences, false) - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) - val decor: View = window.decorView - - AppCompatDelegate.setDefaultNightMode(when ((sharedPreferences.getString("app_theme", "2")?.toInt())) { - 0 -> { - decor.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - AppCompatDelegate.MODE_NIGHT_NO - } - 1 -> { - decor.systemUiVisibility = 0; - AppCompatDelegate.MODE_NIGHT_YES - } - 2 -> { - decor.systemUiVisibility = if (resources.configuration.uiMode and - Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) 0 else View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - } - else -> AppCompatDelegate.MODE_NIGHT_UNSPECIFIED - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/illusionware/npsbrowser/activities/MainActivity.kt b/app/src/main/java/com/illusionware/npsbrowser/activities/MainActivity.kt index a1468e2..82bcf97 100644 --- a/app/src/main/java/com/illusionware/npsbrowser/activities/MainActivity.kt +++ b/app/src/main/java/com/illusionware/npsbrowser/activities/MainActivity.kt @@ -1,58 +1,296 @@ package com.illusionware.npsbrowser.activities import android.os.Bundle -import android.view.MenuItem +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.clickable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.GridView +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.fragment.app.FragmentManager +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.illusionware.npsbrowser.Preferences import com.illusionware.npsbrowser.R -import com.illusionware.npsbrowser.fragments.mainactivity.MainActivityFragment +import com.illusionware.npsbrowser.ui.theme.ColorAccent +import com.illusionware.npsbrowser.ui.theme.NPSBrowserTheme +import com.illusionware.npsbrowser.ui.theme.Typography +import com.illusionware.npsbrowser.ui.components.* +import com.illusionware.npsbrowser.ui.pages.SettingsScreen +import com.illusionware.npsbrowser.ui.pages.OnBoarding +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking -class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener { +class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() super.onCreate(savedInstanceState) - setTheme(R.style.NPSTheme) - setContentView(R.layout.activity_main) - setSupportActionBar(findViewById(R.id.toolbar)) - supportFragmentManager.addOnBackStackChangedListener(this); - - if (supportFragmentManager.backStackEntryCount > 0) { - showUpButton() + setContent { + Router() + } + } +} + +@Composable +fun Router() { + val prefs = Preferences(LocalContext.current) + var theme by remember { runBlocking { mutableStateOf(prefs.theme.first()) } } + var seenOnboarding by remember { runBlocking { mutableStateOf(prefs.seenOnboarding.first()) } } + val navController = rememberNavController() + + val darkTheme = when (theme) { + 0 -> false + 1 -> true + else -> isSystemInDarkTheme() + } + + NPSBrowserTheme(darkTheme = darkTheme) { + Surface( + modifier = Modifier.fillMaxSize(), + color = if (seenOnboarding) MaterialTheme.colorScheme.background else ColorAccent + ) { + NavHost( + navController = navController, + startDestination = if (seenOnboarding) "Home" else "Onboarding", + ) { + composable(route = "Home") { + HomePage( + navigateToSettings = { navController.navigate("Settings") } + ) + } + composable(route = "Settings") { + SettingsScreen( + navigationGoBack = { navController.popBackStack() }, + onThemeChange = { v -> theme = v }, + ) + } + composable(route = "Onboarding") { + OnBoarding( + navigateToHome = { + navController.popBackStack() + seenOnboarding = true + navController.navigate("Home") + }, + darkTheme = darkTheme + ) + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +@Preview() +fun HomePage(navigateToSettings: () -> Unit = {}) { + var isSearchOpen by rememberSaveable { mutableStateOf(false)} + var searchQuery by rememberSaveable { mutableStateOf("")} + val searchBar = remember { FocusRequester() } + var layoutDialogOpen by remember { mutableStateOf(false) } + + Scaffold( + topBar = { + TopAppBar( + title = { + if (!isSearchOpen) { + Text(text = stringResource(id = R.string.app_name)) + } + }, + actions = { + if (isSearchOpen) { + NPSIconButton( + tooltip = "Close Search", + onClick = { isSearchOpen = false } + ){ + Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "Close Search") + } + Spacer(Modifier.width(12.dp)) + TextField( + value = searchQuery, + onValueChange = {str -> searchQuery = str}, + Modifier + .weight(1.0f) + .focusRequester(searchBar), + placeholder = { + Text(text = stringResource(id = R.string.search), style = Typography.bodyLarge) + }, + singleLine = true, + colors = TextFieldDefaults.colors( + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + disabledContainerColor = Color.Transparent, + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + ), + ) + Spacer(Modifier.width(12.dp)) + if (searchQuery.isNotEmpty()) { + NPSIconButton( + tooltip = "Clear Search", + onClick = { searchQuery = "" } + ){ + Icon(imageVector = Icons.Filled.Close, contentDescription = "Clear Search") + } + } + + LaunchedEffect(Unit) { + searchBar.requestFocus() + } + } + if (!isSearchOpen) { + NPSIconButton( + tooltip = stringResource(id = R.string.search_tooltip), + onClick = { isSearchOpen = true } + ){ + Icon(imageVector = Icons.Filled.Search, contentDescription = stringResource( + id = R.string.search_tooltip + )) + } + } + NPSIconButton( + tooltip = stringResource(id = R.string.layout), + onClick = { layoutDialogOpen = true } + ) { + Icon(imageVector = Icons.Filled.GridView, contentDescription = stringResource( + id = R.string.layout + )) + } + NPSIconButton( + tooltip = stringResource(id = R.string.title_activity_settings), + onClick = { navigateToSettings() } + ){ + Icon(imageVector = Icons.Filled.Settings, contentDescription = stringResource( + id = R.string.title_activity_settings + )) + } + } + ) + } + ) { padding -> + Column(Modifier.padding(padding)) { + NoTsvs(navigateToSettings = navigateToSettings) + } + // TODO: only show NoTsvs when there are no Tsvs loaded + + if (layoutDialogOpen) { + LayoutDialog(onDismiss = { layoutDialogOpen = false }) + } + } +} + +@Composable +@Preview +fun NoTsvs(padding: PaddingValues = PaddingValues(0.dp), navigateToSettings: () -> Unit = {}) { + Column( + Modifier + .fillMaxSize() + .padding(padding), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(id = R.drawable.ph_info), + contentDescription = "Info", + tint = MaterialTheme.colorScheme.outline, + modifier = Modifier.size(92.dp) + ) + Spacer(modifier = Modifier.size(8.dp)) + Text( + text = "You haven't added any TSVs", + color = MaterialTheme.colorScheme.outline, + style = Typography.bodyLarge + ) + Row { + Text( + text = "Add your TSV files from the ", + color = MaterialTheme.colorScheme.outline, + style = Typography.bodyLarge + ) + Text( + text = "settings", + color = ColorAccent, + style = Typography.bodyLarge, + modifier = Modifier.clickable { + navigateToSettings() + } + ) + } + } +} + +@Composable +fun LayoutDialog(onDismiss: () -> Unit = {}) { + val prefs = Preferences(LocalContext.current) + val layout = prefs.layout.collectAsState(initial = "") + + NPSAlertDialog( + onDismiss = onDismiss, + title = stringResource(id = R.string.layout), + buttons = { + TextButton(onClick = onDismiss) { + Text(text = "Cancel") + } + } + ) { + Column { + NPSRadioButton( + text = "List", + selected = layout.value == 0, + onClick = { CoroutineScope(Dispatchers.IO).launch { prefs.changeLayout(0) } }, + ) + NPSRadioButton( + text = "Grid", + selected = layout.value == 1, + onClick = { CoroutineScope(Dispatchers.IO).launch { prefs.changeLayout(1) } }, + ) } - - if (savedInstanceState == null) { - supportFragmentManager.beginTransaction() - .replace(R.id.container, MainActivityFragment()) - .commitNow() - } - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - supportFragmentManager.popBackStack() - return true - } - } - return super.onOptionsItemSelected(item) - } - - override fun onBackStackChanged() { - // enable Up button only if there are entries on the backstack - if (supportFragmentManager.backStackEntryCount < 1) { - hideUpButton() - } else { - showUpButton() - } - } - - private fun showUpButton() { - supportActionBar!!.setDisplayHomeAsUpEnabled(true) - } - - private fun hideUpButton() { - supportActionBar!!.setDisplayHomeAsUpEnabled(false) } } diff --git a/app/src/main/java/com/illusionware/npsbrowser/adapters/AppAdapter.kt b/app/src/main/java/com/illusionware/npsbrowser/adapters/AppAdapter.kt deleted file mode 100644 index c0f61cb..0000000 --- a/app/src/main/java/com/illusionware/npsbrowser/adapters/AppAdapter.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.illusionware.npsbrowser.adapters - -import android.content.Context -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.core.content.res.ResourcesCompat -import com.github.wrdlbrnft.modularadapter.ModularAdapter -import com.github.wrdlbrnft.sortedlistadapter.SortedListAdapter -import com.illusionware.npsbrowser.AppData -import com.illusionware.npsbrowser.R -import com.illusionware.npsbrowser.activities.MainActivity -import com.illusionware.npsbrowser.fragments.SingleAppFragment -import com.illusionware.npsbrowser.fragments.mainactivity.MainActivityFragment -import java.util.Comparator - -private val comparator: Comparator = SortedListAdapter.ComparatorBuilder() - .setOrderForModel(AppData::class.java) { a, b -> a.title?.compareTo(b.title!!)!! } - .build() - -class AppAdapter(val context: Context) : SortedListAdapter(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(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 - } -} diff --git a/app/src/main/java/com/illusionware/npsbrowser/fragments/SettingsFragment.kt b/app/src/main/java/com/illusionware/npsbrowser/fragments/SettingsFragment.kt deleted file mode 100644 index a47cf70..0000000 --- a/app/src/main/java/com/illusionware/npsbrowser/fragments/SettingsFragment.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.illusionware.npsbrowser.fragments - -import android.os.Bundle -import android.view.* -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN -import androidx.preference.PreferenceFragmentCompat -import androidx.preference.PreferenceManager -import com.illusionware.npsbrowser.R - -class SettingsFragment : Fragment() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - PreferenceManager.setDefaultValues(requireContext(), R.xml.root_preferences, false) - activity?.supportFragmentManager - ?.beginTransaction() - ?.replace( - R.id.prefs, - PrefsFragment() - ) - ?.setTransition(TRANSIT_FRAGMENT_OPEN) - ?.commit() - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - activity?.title = resources.getString(R.string.title_activity_settings) - return inflater.inflate(R.layout.settings_fragment, container, false) - } - - class PrefsFragment : PreferenceFragmentCompat() { - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - setPreferencesFromResource(R.xml.root_preferences, rootKey) - PreferenceManager.setDefaultValues(requireContext(), R.xml.root_preferences, false) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/illusionware/npsbrowser/fragments/SingleAppFragment.kt b/app/src/main/java/com/illusionware/npsbrowser/fragments/SingleAppFragment.kt deleted file mode 100644 index f439e4c..0000000 --- a/app/src/main/java/com/illusionware/npsbrowser/fragments/SingleAppFragment.kt +++ /dev/null @@ -1,191 +0,0 @@ -package com.illusionware.npsbrowser.fragments - -import android.app.DownloadManager -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context.CLIPBOARD_SERVICE -import android.content.Context.DOWNLOAD_SERVICE -import android.net.Uri -import android.os.Bundle -import android.os.Environment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.ImageView -import android.widget.TextView -import android.widget.Toast -import androidx.fragment.app.Fragment -import androidx.swiperefreshlayout.widget.CircularProgressDrawable -import com.bumptech.glide.Glide -import com.illusionware.npsbrowser.AppData -import com.illusionware.npsbrowser.R - -class SingleAppFragment : Fragment() { - private val baseStoreURL = "https://store.playstation.com/store/api/chihiro/00_09_000/container" - - companion object { - lateinit var app : AppData - } - - // TODO: either use binding or make vars for all the views - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - super.onCreateView(inflater, container, savedInstanceState) - activity?.title = app.title - val view = inflater.inflate(R.layout.single_app_fragment, container, false) - val downloadManager = requireActivity().getSystemService(DOWNLOAD_SERVICE) as DownloadManager - - view?.findViewById(R.id.singleAppTitle)?.text = "[${app.titleID}] ${app.title}" - if (app.fileSize != null) { - view?.findViewById(R.id.singleAppSize)?.text = prettifySize() - } else { - view?.findViewById(R.id.singleAppSizeTitle)?.visibility = View.INVISIBLE - } - view?.findViewById(R.id.singleAppContentId)?.text = app.contentID - view?.findViewById(R.id.singleAppFwVer)?.text = app.minFW - // TODO: get and set app icon - if (app.minFW.isNullOrBlank()) { - view?.findViewById(R.id.singleAppFwVerTitle)?.visibility = View.INVISIBLE - } - if (app.license == "MISSING") { - view?.findViewById(R.id.singleAppError)?.text = getString(R.string.no_license) - view?.findViewById