optimize ui

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/89/head
androidacy-user 3 years ago
parent 7403a285e3
commit 7182b2f233

@ -8,6 +8,7 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Color
@ -57,6 +58,8 @@ import com.fox2code.mmm.utils.io.net.Http.Companion.cleanDnsCache
import com.fox2code.mmm.utils.io.net.Http.Companion.hasWebView
import com.fox2code.mmm.utils.room.ReposListDatabase
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.progressindicator.LinearProgressIndicator
import org.matomo.sdk.extra.TrackHelper
import timber.log.Timber
@ -81,6 +84,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
private var moduleListOnline: RecyclerView? = null
private var searchCard: CardView? = null
private var searchView: SearchView? = null
private var rebootFab: FloatingActionButton? = null
private var initMode = false
private var runtimeUtils: RuntimeUtils? = null
@ -113,9 +117,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
// track enabled repos
Thread {
val db = Room.databaseBuilder(
applicationContext,
ReposListDatabase::class.java,
"ReposList.db"
applicationContext, ReposListDatabase::class.java, "ReposList.db"
).build()
val repoDao = db.reposListDao()
val repos = repoDao.getAll()
@ -175,6 +177,11 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
searchView = findViewById(R.id.search_bar)
val searchView = searchView!!
searchView.isIconified = true
// when the search view is collapsed or user hits x, hide the search view
searchView.setOnCloseListener {
searchView.visibility = View.GONE
false
}
moduleViewAdapter = ModuleViewAdapter()
moduleViewAdapterOnline = ModuleViewAdapter()
val moduleList = moduleList!!
@ -190,14 +197,47 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
updateBlurState()
//hideActionBar();
runtimeUtils!!.checkShowInitialSetup(this, this)
rebootFab = findViewById(R.id.reboot_fab)
val rebootFab = rebootFab!!
// set on click listener for reboot fab
rebootFab.setOnClickListener {
// show reboot dialog with options to reboot, reboot to recovery, bootloader, or edl, and use RuntimeUtils to reboot
val rebootDialog = MaterialAlertDialogBuilder(this@MainActivity)
.setTitle(R.string.reboot)
.setItems(
arrayOf(
getString(R.string.reboot),
getString(R.string.reboot_recovery),
getString(R.string.reboot_bootloader),
getString(R.string.reboot_edl)
)
) { _: DialogInterface?, which: Int ->
when (which) {
0 -> RuntimeUtils.reboot(this@MainActivity, RuntimeUtils.RebootMode.REBOOT)
1 -> RuntimeUtils.reboot(this@MainActivity, RuntimeUtils.RebootMode.RECOVERY)
2 -> RuntimeUtils.reboot(this@MainActivity, RuntimeUtils.RebootMode.BOOTLOADER)
3 -> RuntimeUtils.reboot(this@MainActivity, RuntimeUtils.RebootMode.EDL)
}
}
.setNegativeButton(R.string.cancel, null)
.create()
rebootDialog.show()
}
val searchCard = searchCard!!
// copy reboot fab style to search card
searchCard.elevation = rebootFab.elevation
searchCard.translationY = rebootFab.translationY
searchCard.foreground = rebootFab.foreground
moduleList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState != RecyclerView.SCROLL_STATE_IDLE) searchView.clearFocus()
// hide search view when scrolling
// hide search view and reboot fab when scrolling
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
searchCard.animate().translationY(-searchCard.height.toFloat())
.setInterpolator(AccelerateInterpolator(2f)).start()
rebootFab.animate().translationY(rebootFab.height.toFloat())
.setInterpolator(AccelerateInterpolator(2f)).start()
}
}
@ -207,6 +247,8 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
if (dy < 0) {
searchCard.animate().translationY(0f)
.setInterpolator(DecelerateInterpolator(2f)).start()
rebootFab.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f))
.start()
}
}
})
@ -218,6 +260,8 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
searchCard.animate().translationY(-searchCard.height.toFloat())
.setInterpolator(AccelerateInterpolator(2f)).start()
rebootFab.animate().translationY(rebootFab.height.toFloat())
.setInterpolator(AccelerateInterpolator(2f)).start()
}
}
@ -227,10 +271,10 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
if (dy < 0) {
searchCard.animate().translationY(0f)
.setInterpolator(DecelerateInterpolator(2f)).start()
rebootFab.animate().translationY(0f)
}
}
})
searchCard.radius = searchCard.height / 2f
searchView.minimumHeight = FoxDisplay.dpToPixel(16f)
searchView.imeOptions = EditorInfo.IME_ACTION_SEARCH or EditorInfo.IME_FLAG_NO_FULLSCREEN
searchView.setOnQueryTextListener(this)
@ -390,19 +434,20 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
if (BuildConfig.DEBUG) Timber.i("Check Update")
// update repos
if (hasWebView()) {
val updateListener: SyncManager.UpdateListener = object : SyncManager.UpdateListener {
override fun update(value: Double) {
runOnUiThread(if (max == 0) Runnable {
progressIndicator.setProgressCompat(
(value * PRECISION).toInt(), true
)
} else Runnable {
progressIndicator.setProgressCompat(
(value * PRECISION * 0.75f).toInt(), true
)
})
val updateListener: SyncManager.UpdateListener =
object : SyncManager.UpdateListener {
override fun update(value: Double) {
runOnUiThread(if (max == 0) Runnable {
progressIndicator.setProgressCompat(
(value * PRECISION).toInt(), true
)
} else Runnable {
progressIndicator.setProgressCompat(
(value * PRECISION * 0.75f).toInt(), true
)
})
}
}
}
RepoManager.getINSTANCE()!!.update(updateListener)
}
// various notifications
@ -525,7 +570,6 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
moduleViewListBuilderOnline.setHeaderPx(statusBarHeight)
moduleViewListBuilder.setFooterPx(FoxDisplay.dpToPixel(4f) + bottomInset + searchCard!!.height)
moduleViewListBuilderOnline.setFooterPx(FoxDisplay.dpToPixel(4f) + bottomInset + searchCard!!.height)
searchCard!!.radius = searchCard!!.height / 2f
moduleViewListBuilder.updateInsets()
//this.actionBarBlur.invalidate();
overScrollInsetTop = statusBarHeight
@ -611,15 +655,16 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
progressIndicator!!.max = PRECISION
}
if (BuildConfig.DEBUG) Timber.i("Check Update")
val updateListener: SyncManager.UpdateListener = object : SyncManager.UpdateListener {
override fun update(value: Double) {
runOnUiThread {
progressIndicator!!.setProgressCompat(
(value * PRECISION).toInt(), true
)
val updateListener: SyncManager.UpdateListener =
object : SyncManager.UpdateListener {
override fun update(value: Double) {
runOnUiThread {
progressIndicator!!.setProgressCompat(
(value * PRECISION).toInt(), true
)
}
}
}
}
RepoManager.getINSTANCE()!!.update(updateListener)
runOnUiThread {
progressIndicator!!.setProgressCompat(PRECISION, true)

@ -98,8 +98,18 @@ class AndroidacyRepoData(cacheRoot: File?, testMode: Boolean) : RepoData(
editor.remove("pref_androidacy_api_token")
editor.apply()
return false
} else {
val handler = Handler(Looper.getMainLooper())
handler.post {
Toast.makeText(
INSTANCE,
INSTANCE!!.getString(R.string.androidacy_api_error, e.errorCode),
Toast.LENGTH_LONG
).show()
}
}
throw e
Timber.w(e)
false
} catch (e: JSONException) {
// response is not JSON
Timber.w("Invalid token, resetting...")

@ -38,6 +38,7 @@ import com.fox2code.mmm.androidacy.AndroidacyUtil
import com.fox2code.mmm.module.ActionButtonType
import com.fox2code.mmm.utils.FastException
import com.fox2code.mmm.utils.IntentHelper
import com.fox2code.mmm.utils.RuntimeUtils
import com.fox2code.mmm.utils.io.Files.Companion.copy
import com.fox2code.mmm.utils.io.Files.Companion.fixJavaZipHax
import com.fox2code.mmm.utils.io.Files.Companion.fixSourceArchiveShit
@ -636,24 +637,25 @@ class InstallerActivity : FoxActivity() {
}
setDisplayHomeAsUpEnabled(true)
progressIndicator!!.visibility = View.GONE
// This should be improved ?
val rbtCmd =
"/system/bin/svc power reboot || /system/bin/reboot || setprop sys.powerctl reboot"
rebootFloatingButton!!.setOnClickListener { _: View? ->
if (warnReboot || MainApplication.shouldPreventReboot()) {
if (MainApplication.shouldPreventReboot()) {
// toast and do nothing
Toast.makeText(
this,
R.string.install_terminal_reboot_prevented,
Toast.LENGTH_SHORT
).show()
} else {
val builder = MaterialAlertDialogBuilder(this)
builder.setTitle(R.string.install_terminal_reboot_now)
.setMessage(R.string.install_terminal_reboot_now_message)
.setCancelable(false).setIcon(
R.drawable.ic_reboot_24
).setPositiveButton(R.string.ok) { _: DialogInterface?, _: Int ->
Shell.cmd(rbtCmd).submit()
RuntimeUtils.reboot(this, RuntimeUtils.RebootMode.REBOOT)
}
.setNegativeButton(R.string.no) { x: DialogInterface, _: Int -> x.dismiss() }
.show()
} else {
Shell.cmd(rbtCmd).submit()
}
}
rebootFloatingButton!!.isEnabled = true

@ -27,7 +27,7 @@ import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
class ModuleManager private constructor() : SyncManager() {
private val moduleInfos: HashMap<String, LocalModuleInfo> = HashMap()
private var moduleInfos: HashMap<String, LocalModuleInfo> = HashMap()
private val bootPrefs: SharedPreferences = MainApplication.bootSharedPreferences!!
private var updatableModuleCount = 0
@ -200,12 +200,15 @@ class ModuleManager private constructor() : SyncManager() {
}
}
val modules: HashMap<String, LocalModuleInfo>
var modules: HashMap<String, LocalModuleInfo> = HashMap()
get() {
afterScan()
return moduleInfos
}
set(value) {
moduleInfos = value
field = value
}
@Suppress("unused")
fun getUpdatableModuleCount(): Int {
afterScan()

@ -43,7 +43,7 @@ class ModuleHolder : Comparable<ModuleHolder?> {
constructor(notificationType: NotificationType) {
moduleId = ""
this.notificationType = Objects.requireNonNull(notificationType)
this.notificationType = notificationType
separator = null
footerPx = -1
}
@ -106,17 +106,16 @@ class ModuleHolder : Comparable<ModuleHolder?> {
val type: Type
get() = if (footerPx != -1) {
Timber.i("Module %s is footer", moduleId)
Type.FOOTER
} else if (separator != null) {
Timber.i("Module %s is separator", moduleId)
Type.SEPARATOR
} else if (notificationType != null) {
Timber.i("Module %s is notification", moduleId)
Type.NOTIFICATION
} else if (moduleInfo == null) {
Timber.i("Module %s is null and probably is a remote module", moduleId)
Type.INSTALLABLE
} else if (moduleInfo!!.versionCode < moduleInfo!!.updateVersionCode || repoModule != null && moduleInfo!!.versionCode < repoModule!!.moduleInfo.versionCode) {
Timber.i("Module %s is updateable", moduleId)
var ignoreUpdate = false
try {
if (getSharedPreferences("mmm")?.getStringSet("pref_background_update_check_excludes", HashSet())!!
@ -195,6 +194,7 @@ class ModuleHolder : Comparable<ModuleHolder?> {
Type.UPDATABLE
}
} else {
Timber.i("Module %s is installed", moduleId)
Type.INSTALLED
}

@ -28,10 +28,15 @@ import com.fox2code.mmm.R
import com.fox2code.mmm.SetupActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.topjohnwu.superuser.Shell
import timber.log.Timber
@Suppress("UNUSED_PARAMETER")
class RuntimeUtils {
enum class RebootMode {
REBOOT, RECOVERY, BOOTLOADER, EDL
}
@SuppressLint("RestrictedApi")
private fun ensurePermissions(context: Context, activity: MainActivity) {
if (BuildConfig.DEBUG) Timber.i("Ensure Permissions")
@ -269,4 +274,46 @@ class RuntimeUtils {
prefs.edit().putLong("ugsns4", System.currentTimeMillis()).apply()
Timber.i("showUpgradeSnackbar done")
}
companion object {
fun reboot(mainActivity: FoxActivity, reboot: RebootMode) {
// reboot based on the reboot cmd from the enum we were passed
when (reboot) {
RebootMode.REBOOT -> {
showRebootDialog(mainActivity) {
Shell.cmd("/system/bin/svc power reboot || /system/bin/reboot").submit()
}
}
RebootMode.RECOVERY -> {
// KEYCODE_POWER = 26, hide incorrect "Factory data reset" message
showRebootDialog(mainActivity) {
Shell.cmd("/system/bin/input keyevent 26").submit()
}
}
RebootMode.BOOTLOADER -> {
showRebootDialog(mainActivity) {
Shell.cmd("/system/bin/svc power reboot bootloader || /system/bin/reboot bootloader")
.submit()
}
}
RebootMode.EDL -> {
showRebootDialog(mainActivity) {
Shell.cmd("/system/bin/reboot edl").submit()
}
}
}
}
private fun showRebootDialog(mainActivity: FoxActivity, function: () -> Unit) {
val dialog = MaterialAlertDialogBuilder(mainActivity)
.setTitle(R.string.reboot)
.setMessage(R.string.install_terminal_reboot_now_message)
.setPositiveButton(R.string.reboot) { _, _ ->
function()
}
.setNegativeButton(R.string.cancel) { _, _ -> }
.create()
dialog.show()
}
}
}

@ -10,6 +10,7 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.Uri
import android.os.Build
@ -181,6 +182,9 @@ enum class Http {;
}
companion object {
private var connectivityListener: ConnectivityManager.NetworkCallback? = null
private var lastConnectivityResult: Boolean = false
private var lastConnectivityCheck: Long = 0
private var limitedRetries: Int = 0
private var httpClient: OkHttpClient? = null
private var httpClientDoH: OkHttpClient? = null
@ -719,15 +723,52 @@ enum class Http {;
@JvmStatic
fun hasConnectivity(context: Context): Boolean {
// cache result for 10 seconds so we don't spam the system
if (System.currentTimeMillis() - lastConnectivityCheck < 10000) {
return lastConnectivityResult
}
// Check if we have internet connection using connectivity manager
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
// are we connected to a network with internet capabilities?
val networkCapabilities =
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
return networkCapabilities != null && networkCapabilities.hasCapability(
val systemSaysYes = networkCapabilities != null && networkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_INTERNET
)
Timber.d("System says we have internet: $systemSaysYes")
// if we don't already have a listener, add one, so we can invalidate the cache when the network changes
if (connectivityListener == null) {
connectivityListener = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
Timber.d("Network became available")
lastConnectivityCheck = 0
}
override fun onLost(network: Network) {
super.onLost(network)
Timber.d("Network became unavailable")
lastConnectivityCheck = 0
}
}
connectivityManager.registerDefaultNetworkCallback(connectivityListener!!)
}
if (!systemSaysYes) return false
// check ourselves
val hasInternet = try {
val resp = doHttpGet("https://production-api.androidacy.com/ping", false)
val respString = String(resp)
Timber.d("Ping response: $respString")
true
} catch (e: HttpException) {
Timber.e(e, "Failed to check internet connection")
false
}
Timber.d("We say we have internet: $hasInternet")
lastConnectivityCheck = System.currentTimeMillis()
lastConnectivityResult = systemSaysYes && hasInternet
return lastConnectivityResult
}
}
}

@ -0,0 +1,17 @@
<!--
~ Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,5V2L8,6l4,4V7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93C20,8.58 16.42,5 12,5z" />
<path
android:fillColor="@android:color/white"
android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z" />
</vector>

@ -0,0 +1,7 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<!-- search icon -->
<path android:fillColor="@android:color/white"
android:pathData="M20.71,19.29l-3.89,-3.89C17.54,14.21 18,13.16 18,12c0,-3.31 -2.69,-6 -6,-6s-6,2.69 -6,6s2.69,6 6,6c1.16,0 2.21,-0.46 2.99,-1.2l3.89,3.89c0.39,0.39 1.02,0.39 1.41,0l0,0c0.39,-0.39 0.39,-1.02 0,-1.41zM8,12c0,-2.21 1.79,-4 4,-4s4,1.79 4,4s-1.79,4 -4,4S8,14.21 8,12z"/>
</vector>

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information.
-->
<!-- This is the background for the search bar in the main screen. it should look like a floating action button when the search bar is not focused, and like a text field when it is. -->
<!-- Path: app\src\main\res\drawable\search_bar_background.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.12" android:color="?attr/colorOnSurface" android:state_activated="true" android:state_focused="true" android:state_hovered="true" android:state_pressed="true" android:state_selected="true" android:state_window_focused="true" />
<item android:alpha="0.12" android:color="?attr/colorOnSurface" android:state_activated="true" android:state_focused="true" android:state_hovered="true" android:state_pressed="true" android:state_selected="true" />
<item android:alpha="0.12" android:color="?attr/colorOnSurface" android:state_activated="true" android:state_focused="true" android:state_hovered="true" android:state_pressed="true" />
<item android:alpha="0.12" android:color="?attr/colorOnSurface" android:state_activated="true" android:state_focused="true" android:state_hovered="true" />
<item android:alpha="0.12" android:color="?attr/colorOnSurface" android:state_activated="true" android:state_focused="true" />
<item android:alpha="0.12" android:color="?attr/colorOnSurface" android:state_activated="true" />
<item android:alpha="0.12" android:color="?attr/colorOnSurface" />
</selector>

@ -64,34 +64,51 @@
app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
app:layout_constraintEnd_toEndOf="parent"
tools:ignore="RtlHardcoded">
<!--
setting high app:cardCornerRadius is not supported on some versions
so we must use code to get a round appearance.
-->
<!-- reboot fab, floating to the left of the search bar -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/reboot_fab"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="2dp"
android:clickable="true"
android:contentDescription="@string/reboot"
android:src="@drawable/baseline_restart_alt_24"
android:visibility="visible"
app:fabSize="mini"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/search_bar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:focusable="true" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/search_card"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:shape="ring"
android:layout_height="56dp"
android:visibility="visible"
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="0dp"
app:cardElevation="6dp"
android:layout_marginBottom="2dp"
app:cardPreventCornerOverlap="true"
app:strokeColor="@android:color/transparent"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Material3.Corner.Medium"
app:cardBackgroundColor="@color/system_accent1_100"
app:strokeWidth="0dp">
<androidx.appcompat.widget.SearchView
android:id="@+id/search_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:padding="2dp"
android:visibility="visible"
app:iconifiedByDefault="true"
app:useDrawerArrowDrawable="true" />
android:background="@null"
app:useDrawerArrowDrawable="true"
android:alpha="1"
tools:ignore="DuplicateClickableBoundsCheck" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<!-- bottom md3 navigation bar -->
<!-- used for local and remote module list -->

@ -367,4 +367,12 @@
<string name="error_creating_repos_database">Could not create repos db</string>
<string name="error_creating_modulelistcache_database">Failed to create module cache db</string>
<string name="blur_performance_warning_summary">Device is not compatible with blur</string>
<string name="reboot">Reboot</string>
<string name="search">Search</string>
<string name="reboot_system">Reboot normally</string>
<string name="reboot_recovery">Reboot to recovery</string>
<string name="reboot_bootloader">Reboot to bootloader</string>
<string name="reboot_edl">Reboot to EDL mode</string>
<string name="install_terminal_reboot_prevented">Reboot is disabled in app settings</string>
<string name="androidacy_api_error">Error while communicating with API: %d</string>
</resources>

Loading…
Cancel
Save