v1.0.8: Improve offline pages dialog UI - search, dates, cleaner layout

This commit is contained in:
2026-07-27 21:21:03 -06:00
parent f265498ed3
commit c1eec3cf18
5 changed files with 169 additions and 21 deletions

View File

@@ -20,8 +20,8 @@ android {
applicationId = "com.docmost.app" applicationId = "com.docmost.app"
minSdk = 26 minSdk = 26
targetSdk = 34 targetSdk = 34
versionCode = 8 versionCode = 9
versionName = "1.0.7" versionName = "1.0.8"
} }
signingConfigs { signingConfigs {

View File

@@ -1739,39 +1739,83 @@ class SpacesActivity : AppCompatActivity() {
return return
} }
val view = layoutInflater.inflate(R.layout.dialog_search, null) val view = layoutInflater.inflate(R.layout.dialog_cached_pages, null)
val tvTitle = view.findViewById<TextView>(R.id.tvDialogTitle)
val tvSubtitle = view.findViewById<TextView>(R.id.tvDialogSubtitle)
val etSearch = view.findViewById<android.widget.EditText>(R.id.etSearch) val etSearch = view.findViewById<android.widget.EditText>(R.id.etSearch)
val rvResults = view.findViewById<androidx.recyclerview.widget.RecyclerView>(R.id.rvSearchResults) val rvResults = view.findViewById<RecyclerView>(R.id.rvCachedPages)
val tvEmpty = view.findViewById<TextView>(R.id.tvSearchEmpty) val tvEmpty = view.findViewById<TextView>(R.id.tvEmpty)
etSearch.visibility = View.GONE
tvEmpty.visibility = View.GONE val allPages = cachedPages.sortedByDescending { it.updatedAt }.toMutableList()
val filteredPages = mutableListOf<com.docmost.app.data.CachedPageMeta>()
tvTitle.text = getString(R.string.cached_pages)
tvSubtitle.text = getString(R.string.cached_count, allPages.size)
tvEmpty.text = getString(R.string.no_cached_pages_found)
rvResults.layoutManager = LinearLayoutManager(this) rvResults.layoutManager = LinearLayoutManager(this)
val results = cachedPages.toMutableList()
val adapter = object : androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder>() { val dateFormat = java.text.SimpleDateFormat("dd MMM yyyy", java.util.Locale.getDefault())
override fun getItemCount() = results.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder { fun formatDate(millis: Long): String {
val v = layoutInflater.inflate(android.R.layout.simple_list_item_2, parent, false) return try {
return object : androidx.recyclerview.widget.RecyclerView.ViewHolder(v) {} dateFormat.format(java.util.Date(millis))
} catch (_: Exception) {
""
} }
override fun onBindViewHolder(holder: androidx.recyclerview.widget.RecyclerView.ViewHolder, position: Int) { }
val r = results[position]
val text1 = holder.itemView.findViewById<android.widget.TextView>(android.R.id.text1) fun updateList(pages: List<com.docmost.app.data.CachedPageMeta>) {
val text2 = holder.itemView.findViewById<android.widget.TextView>(android.R.id.text2) filteredPages.clear()
text1.text = if (r.icon.isNullOrBlank()) r.title else "${r.icon} ${r.title}" filteredPages.addAll(pages)
text2.text = r.spaceId ?: getString(R.string.offline_mode) rvResults.adapter?.notifyDataSetChanged()
tvEmpty.visibility = if (filteredPages.isEmpty()) View.VISIBLE else View.GONE
rvResults.visibility = if (filteredPages.isEmpty()) View.GONE else View.VISIBLE
}
val adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun getItemCount() = filteredPages.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val v = layoutInflater.inflate(R.layout.item_cached_page, parent, false)
return object : RecyclerView.ViewHolder(v) {}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val r = filteredPages[position]
val tvIcon = holder.itemView.findViewById<TextView>(R.id.tvPageIcon)
val tvItemTitle = holder.itemView.findViewById<TextView>(R.id.tvPageTitle)
val tvDate = holder.itemView.findViewById<TextView>(R.id.tvPageDate)
tvIcon.text = if (r.icon.isNullOrBlank()) "📄" else r.icon
tvItemTitle.text = r.title
tvDate.text = formatDate(r.updatedAt)
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener {
openPageEditor(Page(id = r.pageId, title = r.title, icon = r.icon, spaceId = r.spaceId)) openPageEditor(Page(id = r.pageId, title = r.title, icon = r.icon, spaceId = r.spaceId))
} }
} }
} }
rvResults.adapter = adapter rvResults.adapter = adapter
updateList(allPages)
val maxHeight = (resources.displayMetrics.heightPixels * 0.6).toInt() val maxHeight = (resources.displayMetrics.heightPixels * 0.7).toInt()
rvResults.layoutParams.height = maxHeight rvResults.layoutParams.height = maxHeight
val dialog = BottomSheetDialog(this) etSearch.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
val query = s?.toString()?.trim() ?: ""
if (query.isEmpty()) {
updateList(allPages)
return
}
val lowerQuery = query.lowercase()
val filtered = allPages.filter { it.title.lowercase().contains(lowerQuery) }
updateList(filtered)
}
})
val dialog = com.google.android.material.bottomsheet.BottomSheetDialog(this)
dialog.setContentView(view) dialog.setContentView(view)
dialog.show() dialog.show()
} }

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tvDialogTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="?attr/colorOnSurface"
android:layout_marginBottom="4dp" />
<TextView
android:id="@+id/tvDialogSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="13sp"
android:textColor="?attr/colorOnSurfaceVariant"
android:layout_marginBottom="12dp" />
<EditText
android:id="@+id/etSearch"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="@string/search_cached_pages"
android:inputType="text"
android:textSize="15sp"
android:background="@drawable/bg_search"
android:drawableStart="@drawable/ic_search"
android:drawablePadding="10dp"
android:paddingStart="14dp"
android:paddingEnd="14dp"
android:maxLines="1"
android:layout_marginBottom="8dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvCachedPages"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:clipToPadding="false" />
<TextView
android:id="@+id/tvEmpty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="14sp"
android:textColor="?attr/colorOnSurfaceVariant"
android:padding="32dp"
android:visibility="gone" />
</LinearLayout>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvPageIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center"
android:textSize="22sp"
android:text="📄" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="12dp">
<TextView
android:id="@+id/tvPageTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="?attr/colorOnSurface"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end" />
<TextView
android:id="@+id/tvPageDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="?attr/colorOnSurfaceVariant"
android:layout_marginTop="2dp" />
</LinearLayout>
</LinearLayout>

View File

@@ -128,4 +128,6 @@
<string name="clean_confirm">¿Eliminar todas las páginas descargadas? Las borradores locales se conservarán.</string> <string name="clean_confirm">¿Eliminar todas las páginas descargadas? Las borradores locales se conservarán.</string>
<string name="cache_cleaned">Caché limpiada: %d páginas eliminadas</string> <string name="cache_cleaned">Caché limpiada: %d páginas eliminadas</string>
<string name="cached_count">Páginas en caché: %d</string> <string name="cached_count">Páginas en caché: %d</string>
<string name="search_cached_pages">Buscar páginas guardadas…</string>
<string name="no_cached_pages_found">No se encontraron páginas</string>
</resources> </resources>