From 95c6dbe556492f1bb1e9f7ffafea109da26a006b Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Mon, 2 Nov 2020 19:24:26 +0200 Subject: [PATCH] Change singleapp to fragment, put toolbar in activity, add search bar --- app/build.gradle | 9 +- app/src/main/AndroidManifest.xml | 8 +- .../com/illusionware/npsbrowser/AppData.kt | 60 ++++- .../npsbrowser/activities/MainActivity.kt | 26 +- .../activities/SingleAppActivity.kt | 104 -------- .../npsbrowser/adapters/AppAdapter.kt | 98 ++++--- .../npsbrowser/fragments/SettingsFragment.kt | 29 +- .../npsbrowser/fragments/SingleAppFragment.kt | 115 ++++++++ .../mainactivity/MainActivityFragment.kt | 247 +++++++++++------- ...ic_add_yellow_24dp.xml => ic_add_24dp.xml} | 0 ...24dp.xml => ic_games_placeholder_24dp.xml} | 0 app/src/main/res/drawable/ic_search_24dp.xml | 9 + app/src/main/res/layout/activity_main.xml | 30 ++- app/src/main/res/layout/appbar.xml | 1 + app/src/main/res/layout/main_fragment.xml | 9 +- .../{grid_item.xml => recycler_item_grid.xml} | 3 +- .../{list_item.xml => recycler_item_list.xml} | 3 +- app/src/main/res/layout/settings_fragment.xml | 12 +- ...single_app.xml => single_app_fragment.xml} | 2 +- .../res/menu/{toolbar_main.xml => main.xml} | 7 + app/src/main/res/values-night/colors.xml | 1 + app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 2 +- app/src/main/res/xml/root_preferences.xml | 2 +- build.gradle | 2 +- 26 files changed, 474 insertions(+), 307 deletions(-) delete mode 100644 app/src/main/java/com/illusionware/npsbrowser/activities/SingleAppActivity.kt create mode 100644 app/src/main/java/com/illusionware/npsbrowser/fragments/SingleAppFragment.kt rename app/src/main/res/drawable/{ic_add_yellow_24dp.xml => ic_add_24dp.xml} (100%) rename app/src/main/res/drawable/{ic_games_black_24dp.xml => ic_games_placeholder_24dp.xml} (100%) create mode 100644 app/src/main/res/drawable/ic_search_24dp.xml rename app/src/main/res/layout/{grid_item.xml => recycler_item_grid.xml} (96%) rename app/src/main/res/layout/{list_item.xml => recycler_item_list.xml} (94%) rename app/src/main/res/layout/{activity_single_app.xml => single_app_fragment.xml} (95%) rename app/src/main/res/menu/{toolbar_main.xml => main.xml} (57%) diff --git a/app/build.gradle b/app/build.gradle index ab2f937..30216a3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -57,18 +57,19 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.core:core-ktx:1.3.2' - implementation 'androidx.constraintlayout:constraintlayout:2.0.2' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation 'com.github.doyaaaaaken:kotlin-csv-jvm:0.11.1' + 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.3.0' - implementation 'androidx.navigation:navigation-ui-ktx:2.3.0' + implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1' + implementation 'androidx.navigation:navigation-ui-ktx:2.3.1' implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' implementation 'com.google.android.material:material:1.2.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 76a077a..550722f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,18 +18,12 @@ - + android:configChanges="screenSize|screenLayout|keyboardHidden|orientation"> - \ No newline at end of file diff --git a/app/src/main/java/com/illusionware/npsbrowser/AppData.kt b/app/src/main/java/com/illusionware/npsbrowser/AppData.kt index a35dcf7..c2070ba 100644 --- a/app/src/main/java/com/illusionware/npsbrowser/AppData.kt +++ b/app/src/main/java/com/illusionware/npsbrowser/AppData.kt @@ -1,5 +1,59 @@ package com.illusionware.npsbrowser -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?) \ No newline at end of file +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 as AppData + return "${other.titleID} ${other.title}" == "$titleID $title" + } + return false + } + + override fun isContentTheSameAs(model: T): Boolean { + if (model is AppData) { + val other: AppData = model as AppData + 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/activities/MainActivity.kt b/app/src/main/java/com/illusionware/npsbrowser/activities/MainActivity.kt index dfbb7ca..7f293e9 100644 --- a/app/src/main/java/com/illusionware/npsbrowser/activities/MainActivity.kt +++ b/app/src/main/java/com/illusionware/npsbrowser/activities/MainActivity.kt @@ -2,18 +2,26 @@ package com.illusionware.npsbrowser.activities import android.os.Bundle import android.view.MenuItem +import androidx.fragment.app.FragmentManager import com.illusionware.npsbrowser.R import com.illusionware.npsbrowser.fragments.mainactivity.MainActivityFragment -class MainActivity : BaseActivity() { +class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener { override fun onCreate(savedInstanceState: Bundle?) { 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() + } + if (savedInstanceState == null) { supportFragmentManager.beginTransaction() - .replace(R.id.container, MainActivityFragment.newInstance()) + .replace(R.id.container, MainActivityFragment()) .commitNow() } } @@ -28,12 +36,20 @@ class MainActivity : BaseActivity() { return super.onOptionsItemSelected(item) } - fun showUpButton() { + 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) } - fun hideUpButton() { + private fun hideUpButton() { supportActionBar!!.setDisplayHomeAsUpEnabled(false) } - } diff --git a/app/src/main/java/com/illusionware/npsbrowser/activities/SingleAppActivity.kt b/app/src/main/java/com/illusionware/npsbrowser/activities/SingleAppActivity.kt deleted file mode 100644 index d4d5b92..0000000 --- a/app/src/main/java/com/illusionware/npsbrowser/activities/SingleAppActivity.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.illusionware.npsbrowser.activities - -import android.app.DownloadManager -import android.content.ClipData -import android.content.ClipboardManager -import android.net.Uri -import android.os.Bundle -import android.os.Environment -import android.util.Log -import android.view.View -import android.widget.Toast -import com.illusionware.npsbrowser.AppData -import com.illusionware.npsbrowser.databinding.ActivitySingleAppBinding -import java.lang.Exception - -class SingleAppActivity: BaseActivity() { - companion object { - lateinit var app : AppData - } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val binding = ActivitySingleAppBinding.inflate(layoutInflater) - setContentView(binding.root) - - var downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager - - binding.singleAppTitle.text = "[${app.titleID}] ${app.title}" - if (app.fileSize != null) { - binding.singleAppSize.text = prettifySize() - } else { - binding.singleAppSizeTitle.visibility = View.INVISIBLE - } - binding.singleAppContentId.text = app.contentID - binding.singleAppFwVer.text = app.minFW - // TODO: get and set app icon - if (app.minFW.isNullOrBlank()) { - binding.singleAppFwVerTitle.visibility = View.INVISIBLE - } - if (app.license == "MISSING") { - binding.singleAppError.text = "Warning: No license was provided for this app." - } - if (app.link == "CART ONLY") { - binding.singleAppError.text = "CART ONLY GAME" - binding.singleAppDownloadButton.isEnabled = false - } else if (app.link == "MISSING") { - binding.singleAppError.text = "Warning: No download link was provided for this app." - binding.singleAppDownloadButton.isEnabled = false - } - - binding.singleAppContentId.setOnLongClickListener { - val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager - val clipData = ClipData.newPlainText("Content ID", binding.singleAppContentId.text) - clipboardManager.setPrimaryClip(clipData) - Toast.makeText(this, "Content ID Copied to Clipboard!", Toast.LENGTH_SHORT).show() - true - } - - binding.singleAppDownloadButton.setOnClickListener { - try { - var request = DownloadManager.Request(Uri.parse(app.link)) - request.setTitle(app.title) - request.setDescription("Downloading...") - request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) - request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, app.title + ".pkg") // TODO: set up preference for this - downloadManager.enqueue(request) - } catch (e: Exception) { - Toast.makeText(this, "An error occurred while downloading. please try again", Toast.LENGTH_SHORT).show() - } - } - - binding.singleAppDownloadButton.setOnLongClickListener { - val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager - val clipData = ClipData.newPlainText("PKG URL", app.link) - clipboardManager.setPrimaryClip(clipData) - Toast.makeText(this, "PKG URL Copied to Clipboard", Toast.LENGTH_SHORT).show() - true - } - } - - 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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/illusionware/npsbrowser/adapters/AppAdapter.kt b/app/src/main/java/com/illusionware/npsbrowser/adapters/AppAdapter.kt index c00ea9c..fe45bf1 100644 --- a/app/src/main/java/com/illusionware/npsbrowser/adapters/AppAdapter.kt +++ b/app/src/main/java/com/illusionware/npsbrowser/adapters/AppAdapter.kt @@ -1,33 +1,73 @@ 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 androidx.recyclerview.widget.RecyclerView +import com.github.wrdlbrnft.modularadapter.ModularAdapter +import com.github.wrdlbrnft.sortedlistadapter.SortedListAdapter import com.illusionware.npsbrowser.AppData import com.illusionware.npsbrowser.R -import kotlin.collections.ArrayList +import com.illusionware.npsbrowser.activities.MainActivity +import com.illusionware.npsbrowser.fragments.SingleAppFragment +import com.illusionware.npsbrowser.fragments.mainactivity.MainActivityFragment +import java.util.Comparator -class AppAdapter(private val appsList: ArrayList, val context : Context) : RecyclerView.Adapter() { - private final val LIST_ITEM = 0 - private final val GRID_ITEM = 1 - var isSwitchView = true +private val comparator: Comparator = SortedListAdapter.ComparatorBuilder() + .setOrderForModel(AppData::class.java) { a, b -> a.title?.compareTo(b.title!!)!! } + .build() - inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { - var appTitle: TextView = view.findViewById(R.id.appTitle) - var appRegion: TextView = view.findViewById(R.id.appRegion) - var appMinFW: TextView = view.findViewById(R.id.appMinFW) - var appLastDate: TextView = view.findViewById(R.id.appLastDate) +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.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(parent: ViewGroup, viewType: Int): ViewHolder { + override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup, viewType: Int): AppViewHolder { return if (viewType == LIST_ITEM) { - ViewHolder( LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)) + AppViewHolder( + inflater.inflate( + R.layout.recycler_item_list, + parent, + false + ) + ) } else { - ViewHolder( LayoutInflater.from(parent.context).inflate(R.layout.grid_item, parent, false)) + AppViewHolder( + inflater.inflate( + R.layout.recycler_item_grid, + parent, + false + ) + ) } } @@ -39,34 +79,8 @@ class AppAdapter(private val appsList: ArrayList, val context : Context } } - public fun toggleItemViewType(type: Int): Boolean { + fun toggleItemViewType(type: Int): Boolean { isSwitchView = type == 0 return isSwitchView } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - holder.appTitle.text = appsList[position].title - holder.appTitle.isSelected = true - holder.appRegion.text = appsList[position].region - holder.appMinFW.text = appsList[position].minFW - holder.appMinFW.background = null; - - if (holder.appMinFW.text.isNotBlank()) { - val drawable = ResourcesCompat.getDrawable(context.resources, R.drawable.rect_red, null) - holder.appMinFW.background = drawable; - } - holder.appLastDate.text = appsList[position].lastDateTime - } - - override fun getItemCount(): Int { - return appsList.size - } - - fun updateData(apps: ArrayList) { - appsList.clear() - notifyDataSetChanged() - appsList.addAll(apps) - notifyDataSetChanged() - - } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/illusionware/npsbrowser/fragments/SettingsFragment.kt b/app/src/main/java/com/illusionware/npsbrowser/fragments/SettingsFragment.kt index ae17dc0..8630afe 100644 --- a/app/src/main/java/com/illusionware/npsbrowser/fragments/SettingsFragment.kt +++ b/app/src/main/java/com/illusionware/npsbrowser/fragments/SettingsFragment.kt @@ -1,33 +1,23 @@ package com.illusionware.npsbrowser.fragments import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +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 -import com.illusionware.npsbrowser.activities.BaseActivity -import com.illusionware.npsbrowser.activities.MainActivity -import com.illusionware.npsbrowser.fragments.mainactivity.MainActivityFragment -import kotlinx.android.synthetic.main.appbar.* class SettingsFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (activity!!.supportFragmentManager.backStackEntryCount < 1) { - (activity as MainActivity?)?.showUpButton() - MainActivityFragment().recyclerView?.adapter = MainActivityFragment().viewAdapter - } PreferenceManager.setDefaultValues(context, R.xml.root_preferences, false); activity?.supportFragmentManager ?.beginTransaction() ?.replace( R.id.prefs, - SettingsFragment() + PrefsFragment() ) ?.setTransition(TRANSIT_FRAGMENT_OPEN) ?.commit() @@ -42,24 +32,11 @@ class SettingsFragment : Fragment() { return inflater.inflate(R.layout.settings_fragment, container, false) } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - (activity as BaseActivity?)!!.setSupportActionBar(toolbar) - (activity as BaseActivity?)!!.supportActionBar?.setDisplayHomeAsUpEnabled(true) - //viewModel = ViewModelProviders.of(this).get(MainActivity2ViewModel::class.java) - // TODO: Use the ViewModel - } - - class SettingsFragment : PreferenceFragmentCompat() { + class PrefsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.root_preferences, rootKey) PreferenceManager.setDefaultValues(context, R.xml.root_preferences, false); } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - //(activity as BaseActivity?)!!.setSupportActionBar(toolbar) - super.onActivityCreated(savedInstanceState) - } } } \ 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 new file mode 100644 index 0000000..e6d081b --- /dev/null +++ b/app/src/main/java/com/illusionware/npsbrowser/fragments/SingleAppFragment.kt @@ -0,0 +1,115 @@ +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.TextView +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.illusionware.npsbrowser.AppData +import com.illusionware.npsbrowser.R +import java.lang.Exception + +class SingleAppFragment : Fragment() { + companion object { + lateinit var app : AppData + } + + 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 = "Warning: No license was provided for this app." + } + if (app.link == "CART ONLY") { + view?.findViewById(R.id.singleAppError)?.text = "CART ONLY GAME" + view?.findViewById