From fc94aa665c1e5cacca83e939d0514d718b07b993 Mon Sep 17 00:00:00 2001 From: SamuelZima Date: Mon, 20 Oct 2025 17:47:14 +0200 Subject: [PATCH 01/12] Added Bookmark.kt, BookmarksAdapter.kt and updated MyPreferences.kt - added Bookmark.kt - data class for individual bookmarks, same structure as History.kt - added BookmarksAdapter.kt - for building the recycler view and listeners, very similar style and structure to HistoryAdapter.kt - updated MyPreferences.kt - added bookmark constants, setters and CRUD functions --- .../opencalculator/MyPreferences.kt | 77 ++++++++++ .../opencalculator/bookmarks/Bookmark.kt | 11 ++ .../bookmarks/BookmarksAdapter.kt | 131 ++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 app/src/main/java/com/darkempire78/opencalculator/bookmarks/Bookmark.kt create mode 100644 app/src/main/java/com/darkempire78/opencalculator/bookmarks/BookmarksAdapter.kt diff --git a/app/src/main/java/com/darkempire78/opencalculator/MyPreferences.kt b/app/src/main/java/com/darkempire78/opencalculator/MyPreferences.kt index 7f8239994..99688a422 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/MyPreferences.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/MyPreferences.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.appcompat.app.AppCompatDelegate.* import androidx.preference.PreferenceManager import com.darkempire78.opencalculator.history.History +import com.darkempire78.opencalculator.bookmarks.Bookmark import com.google.gson.Gson class MyPreferences(context: Context) { @@ -31,6 +32,8 @@ class MyPreferences(context: Context) { private const val KEY_MOVE_BACK_BUTTON_LEFT = "darkempire78.opencalculator.MOVE_BACK_BUTTON_LEFT" private const val KEY_NUMBERING_SYSTEM = "darkempire78.opencalculator.NUMBERING_SYSTEM" private const val KEY_SHOW_ON_LOCK_SCREEN = "darkempire78.opencalculator.KEY_SHOW_ON_LOCK_SCREEN" + private const val KEY_BOOKMARKS = "darkempire78.opencalculator.BOOKMARKS_ELEMENTS" + private const val KEY_BOOKMARKS_SIZE = "darkempire78.opencalculator.BOOKMARKS_SIZE" } private val preferences = PreferenceManager.getDefaultSharedPreferences(context) @@ -78,6 +81,12 @@ class MyPreferences(context: Context) { var showOnLockScreen = preferences.getBoolean(KEY_SHOW_ON_LOCK_SCREEN, true) set(value) = preferences.edit().putBoolean(KEY_SHOW_ON_LOCK_SCREEN, value).apply() + private var bookmarks = preferences.getString(KEY_BOOKMARKS, null) + set(value) = preferences.edit().putString(KEY_BOOKMARKS, value).apply() + + var bookmarksSize = preferences.getString(KEY_BOOKMARKS_SIZE, "50") + set(value) = preferences.edit().putString(KEY_BOOKMARKS_SIZE, value).apply() + fun getHistory(): MutableList { val gson = Gson() @@ -120,4 +129,72 @@ class MyPreferences(context: Context) { saveHistory(historyList) } } + + fun getBookmarks(): MutableList { + val gson = Gson() + + val bookmarkJson = preferences.getString(KEY_BOOKMARKS, null) + + return if (bookmarkJson != null) { + try { + val list = gson.fromJson(bookmarkJson, Array::class.java).asList().toMutableList() + list + } catch (e: Exception) { + mutableListOf() + } + } else { + mutableListOf() + } + } + + fun saveBookmarks(bookmarks: List){ + val gson = Gson() + val bookmarks2 = bookmarks.toMutableList() + + // Remove the oldest items from the bookmarks list until bookmarks list length <= limit + while (bookmarksSize!!.toInt() > 0 && bookmarks2.size > bookmarksSize!!.toInt()) { + bookmarks2.removeAt(0) + } + MyPreferences(ctx).bookmarks = gson.toJson(bookmarks2) // Convert to json + } + + fun addBookmark(calculation: String, result: String): Bookmark { + val bookmarksList = getBookmarks() + + // Prevent duplicate bookmarks by same (calculation, result) + val existing = bookmarksList.firstOrNull { it.calculation == calculation && it.result == result } + if (existing != null) { + // Bookmark already exists + return existing + } + + // Create new bookmark and save it + val bookmark = Bookmark(calculation = calculation, result = result) + bookmarksList.add(bookmark) + saveBookmarks(bookmarksList) + return bookmark + } + + fun getBookmarkById(id: String): Bookmark? { + val bookmarks = getBookmarks() + return bookmarks.find { it.id == id } + } + + fun updateBookmarkById(id: String, bookmark: Bookmark) { + val bookmarksList = getBookmarks() + val index = bookmarksList.indexOfFirst { it.id == id } + if (index != -1) { + bookmarksList[index] = bookmark + saveBookmarks(bookmarksList) + } + } + + fun removeBookmarkById(id: String) { + val list = getBookmarks().filterNot { it.id == id } + saveBookmarks(list) + } + + fun isBookmarked(calculation: String, result: String): Boolean { + return getBookmarks().any { it.calculation == calculation && it.result == result } + } } diff --git a/app/src/main/java/com/darkempire78/opencalculator/bookmarks/Bookmark.kt b/app/src/main/java/com/darkempire78/opencalculator/bookmarks/Bookmark.kt new file mode 100644 index 000000000..b9bc69ef7 --- /dev/null +++ b/app/src/main/java/com/darkempire78/opencalculator/bookmarks/Bookmark.kt @@ -0,0 +1,11 @@ +package com.darkempire78.opencalculator.bookmarks + +import com.google.gson.annotations.SerializedName +import java.util.UUID + +data class Bookmark ( + @SerializedName("calculation") var calculation: String, + @SerializedName("result") var result: String, + @SerializedName("time") var time: String = System.currentTimeMillis().toString(), + @SerializedName("id") var id: String = UUID.randomUUID().toString() +) \ No newline at end of file diff --git a/app/src/main/java/com/darkempire78/opencalculator/bookmarks/BookmarksAdapter.kt b/app/src/main/java/com/darkempire78/opencalculator/bookmarks/BookmarksAdapter.kt new file mode 100644 index 000000000..a3d6714af --- /dev/null +++ b/app/src/main/java/com/darkempire78/opencalculator/bookmarks/BookmarksAdapter.kt @@ -0,0 +1,131 @@ +package com.darkempire78.opencalculator.bookmarks + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Context.CLIPBOARD_SERVICE +import android.os.Build +import android.text.format.DateUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import android.widget.Toast +import androidx.recyclerview.widget.RecyclerView +import com.darkempire78.opencalculator.MyPreferences +import com.darkempire78.opencalculator.R + +class BookmarksAdapter( + private var items: MutableList, + private val onElementClick: (value: String) -> Unit, + private val context: Context +) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { + val view = LayoutInflater + .from(parent.context) + .inflate(R.layout.history_item, parent, false) + return VH(view) + } + + override fun getItemCount(): Int = items.size + + override fun onBindViewHolder(holder: VH, position: Int) { + holder.bind(items[position], position) + } + + fun updateList(newItems: MutableList) { + items = newItems + notifyDataSetChanged() + } + + fun appendOne(b: Bookmark) { + items.add(b) + if (items.size > 1) { + notifyItemInserted(items.size - 1) + notifyItemRangeChanged(items.size - 2, 2) + } else { + notifyItemInserted(items.size - 1) + } + } + + fun removeAt(position: Int) { + items.removeAt(position) + notifyItemRemoved(position) + } + + fun clear() { + items.clear() + notifyDataSetChanged() + } + + inner class VH(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val calculation: TextView = itemView.findViewById(R.id.history_item_calculation) + private val result: TextView = itemView.findViewById(R.id.history_item_result) + private val time: TextView = itemView.findViewById(R.id.history_time) + private val separator: View = itemView.findViewById(R.id.history_separator) + private val sameDateSeparator: View = itemView.findViewById(R.id.history_same_date_separator) + + private fun wrapInParenthesis(s: String): String { + return if (s.first() != '(' || s.last() != ')') "($s)" else s + } + + fun bind(b: Bookmark, position: Int) { + calculation.text = b.calculation + result.text = b.result + + if (b.time.isEmpty()) { + time.visibility = View.GONE + } else { + val rel = DateUtils.getRelativeTimeSpanString( + b.time.toLong(), + System.currentTimeMillis(), + DateUtils.DAY_IN_MILLIS, + DateUtils.FORMAT_ABBREV_RELATIVE + ) + time.text = rel + // Keep same grouping to look like history for now: + if (position + 1 < items.size) { + val nextRel = DateUtils.getRelativeTimeSpanString( + items[position + 1].time.toLong(), + System.currentTimeMillis(), + DateUtils.DAY_IN_MILLIS, + DateUtils.FORMAT_ABBREV_RELATIVE + ) + if (nextRel == rel) { + // Show only sameDateSeparator + separator.visibility = View.GONE + sameDateSeparator.visibility = View.VISIBLE + } else { + // Relative times don't match -> show only the main separator + separator.visibility = View.VISIBLE + sameDateSeparator.visibility = View.GONE + } + } else { + separator.visibility = View.VISIBLE + sameDateSeparator.visibility = View.GONE + } + } + + calculation.setOnClickListener { onElementClick.invoke(wrapInParenthesis(b.calculation)) } + result.setOnClickListener { onElementClick.invoke(wrapInParenthesis(b.result)) } + + if (MyPreferences(itemView.context).longClickToCopyValue) { + calculation.setOnLongClickListener { + val cm = itemView.context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager + cm.setPrimaryClip(ClipData.newPlainText(itemView.context.getString(R.string.copied_history_calculation), b.calculation)) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) + Toast.makeText(itemView.context, R.string.value_copied, Toast.LENGTH_SHORT).show() + true + } + result.setOnLongClickListener { + val cm = itemView.context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager + cm.setPrimaryClip(ClipData.newPlainText(itemView.context.getString(R.string.copied_history_result), b.result)) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) + Toast.makeText(itemView.context, R.string.value_copied, Toast.LENGTH_SHORT).show() + true + } + } + } + } +} From b0d236450befbbab1ca43d99742317bfe1de3ef1 Mon Sep 17 00:00:00 2001 From: SamuelZima Date: Sat, 25 Oct 2025 14:20:39 +0200 Subject: [PATCH 02/12] Updated MainActivity.kt and resources MainActivity.kt - added Bookmarks RecyclerView setup - added setSwipeTouchHelperForBookmarks() function to delete a bookmark on swipe as in history - added addBookmark() function which bookmarks a calculation - in onResume added runCatching block to refresh bookmarks layout/activity_main.xml - added RecyckerView for Bookmarks to layout/activity_main.xml, layout-land/activity_main.xml, layout-sw-720dp-land/activity_main.xml values/strings.xml - added nothing_to_bookmark and bookmarked string resources --- .../opencalculator/activities/MainActivity.kt | 88 ++++++++++++++++++- .../main/res/layout-land/activity_main.xml | 13 +++ .../res/layout-sw720dp-land/activity_main.xml | 13 +++ app/src/main/res/layout/activity_main.xml | 13 +++ app/src/main/res/values/strings.xml | 4 + 5 files changed, 130 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt index ba09ba2a5..aed39f6ff 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt @@ -50,6 +50,8 @@ import com.darkempire78.opencalculator.databinding.ActivityMainBinding import com.darkempire78.opencalculator.dialogs.DonationDialog import com.darkempire78.opencalculator.history.History import com.darkempire78.opencalculator.history.HistoryAdapter +import com.darkempire78.opencalculator.bookmarks.Bookmark +import com.darkempire78.opencalculator.bookmarks.BookmarksAdapter import com.darkempire78.opencalculator.util.ScientificMode import com.darkempire78.opencalculator.util.ScientificModeTypes import com.sothree.slidinguppanel.PanelSlideListener @@ -94,6 +96,10 @@ class MainActivity : AppCompatActivity() { private lateinit var historyAdapter: HistoryAdapter private lateinit var historyLayoutMgr: LinearLayoutManager + private lateinit var bookmarksAdapter: BookmarksAdapter + private lateinit var bookmarksLayoutMgr: LinearLayoutManager + private lateinit var bookmarksTouchHelper: ItemTouchHelper + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -183,6 +189,24 @@ class MainActivity : AppCompatActivity() { checkEmptyHistoryForNoHistoryLabel() } + // --- Bookmarks RecyclerView setup --- + bookmarksLayoutMgr = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) + binding.bookmarksRecyclerView.layoutManager = bookmarksLayoutMgr + + bookmarksAdapter = BookmarksAdapter( + items = MyPreferences(this).getBookmarks(), + onElementClick = { value -> updateDisplay(window.decorView, value) }, + context = this + ) + binding.bookmarksRecyclerView.adapter = bookmarksAdapter + + // Scroll to the bottom of the recycle view + if (bookmarksAdapter.itemCount > 0) { + binding.bookmarksRecyclerView.scrollToPosition(bookmarksAdapter.itemCount - 1) + } + + setSwipeTouchHelperForBookmarks() + // --- END Bookmarks RecyclerView setup --- // Set the sliding layout binding.slidingLayout.addPanelSlideListener(object : PanelSlideListener { @@ -404,6 +428,63 @@ class MainActivity : AppCompatActivity() { } } + // Touch swipe deletion of a bookmark same as history + private fun setSwipeTouchHelperForBookmarks() { + val callBack = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean = false + + override fun isItemViewSwipeEnabled(): Boolean { + // Share the same setting as history for now + return MyPreferences(this@MainActivity).deleteHistoryOnSwipe + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val position = viewHolder.bindingAdapterPosition + + val prefs = MyPreferences(this@MainActivity) + val current = prefs.getBookmarks() + if (position in 0 until current.size) { + val removed = current[position] + // Update adapter first for snappy UI + bookmarksAdapter.removeAt(position) + // Persist removal + prefs.removeBookmarkById(removed.id) + } else { + // Fallback: refresh from prefs + bookmarksAdapter.updateList(prefs.getBookmarks()) + } + } + } + bookmarksTouchHelper = ItemTouchHelper(callBack) + bookmarksTouchHelper.attachToRecyclerView(binding.bookmarksRecyclerView) + } + + // Function to bookmark a calculation + fun addBookmark(menuItem: MenuItem) { + val calculation = binding.input.text.toString() + val shownResult = binding.resultDisplay.text.toString() + + if (calculation.isEmpty() && shownResult.isEmpty()) { + Toast.makeText(this, R.string.nothing_to_bookmark, Toast.LENGTH_SHORT).show() + return + } + + val result = if (shownResult.isNotEmpty()) shownResult else calculation + + val prefs = MyPreferences(this) + val bookmark = prefs.addBookmark(calculation, result) + // Refresh from prefs to reflect dedupe/trim behavior + bookmarksAdapter.updateList(prefs.getBookmarks()) + if (bookmarksAdapter.itemCount > 0) { + binding.bookmarksRecyclerView.scrollToPosition(bookmarksAdapter.itemCount - 1) + } + Toast.makeText(this, R.string.bookmarked, Toast.LENGTH_SHORT).show() + } + fun openAppMenu(view: View) { val popup = PopupMenu(this, view) val inflater = popup.menuInflater @@ -1383,7 +1464,12 @@ class MainActivity : AppCompatActivity() { // Enable the possibility to show the activity on the lock screen val canShowOnLockScreen = MyPreferences(this).showOnLockScreen handleOnLockScreenAppStatus(canShowOnLockScreen) -} + + // Refresh bookmarks if the view/adapter is available + runCatching { + bookmarksAdapter.updateList(MyPreferences(this).getBookmarks()) + } + } fun checkEmptyHistoryForNoHistoryLabel() { if (historyAdapter.itemCount==0) { diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index faa8a4f74..b07d38d2d 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -432,6 +432,19 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@+id/sliding_layout_button"/> + + + + + + No History Available Show on Lock Screen Keep the calculator visible on your lock screen for quick and easy access. Disable to hide it when your screen is locked. + + + Nothing to bookmark + Bookmarked From c19abd67c927d48c02ac7017275b559a578bb4bf Mon Sep 17 00:00:00 2001 From: SamuelZima Date: Sat, 25 Oct 2025 15:33:40 +0200 Subject: [PATCH 03/12] Added history/bookmarks tabs to the sliding panel with recycler view MainActivity.kt - history/bookmarks tabs setup -> default to History tab - added showHistoryList(), showBookmarksList() functions acitivity_main.xml - changed constraint/borders to the tabs and recycler views in all three layouts strings.xml - added bookmarks tab title --- .../opencalculator/activities/MainActivity.kt | 39 +++++++++++++++++++ .../main/res/layout-land/activity_main.xml | 22 ++++++++--- .../res/layout-sw720dp-land/activity_main.xml | 22 ++++++++--- app/src/main/res/layout/activity_main.xml | 19 ++++++--- app/src/main/res/values/strings.xml | 1 + 5 files changed, 86 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt index aed39f6ff..c16a1fb87 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt @@ -208,6 +208,32 @@ class MainActivity : AppCompatActivity() { setSwipeTouchHelperForBookmarks() // --- END Bookmarks RecyclerView setup --- + // --- History/Bookmarks tabs setup --- + binding.historyBookmarksTabs.addTab(binding.historyBookmarksTabs.newTab().setText(R.string.settings_category_history)) + binding.historyBookmarksTabs.addTab(binding.historyBookmarksTabs.newTab().setText(R.string.bookmarks_tab_title)) + + // Default to History tab + showHistoryList() + binding.slidingLayout.scrollableView = binding.historyRecylcleView + + binding.historyBookmarksTabs.addOnTabSelectedListener(object : com.google.android.material.tabs.TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: com.google.android.material.tabs.TabLayout.Tab) { + when (tab.position) { + 0 -> { // History + showHistoryList() + binding.slidingLayout.scrollableView = binding.historyRecylcleView + } + 1 -> { // Bookmarks + showBookmarksList() + binding.slidingLayout.scrollableView = binding.bookmarksRecyclerView + } + } + } + override fun onTabUnselected(tab: com.google.android.material.tabs.TabLayout.Tab) {} + override fun onTabReselected(tab: com.google.android.material.tabs.TabLayout.Tab) {} + }) + // --- END History/Bookmarks tabs setup --- + // Set the sliding layout binding.slidingLayout.addPanelSlideListener(object : PanelSlideListener { override fun onPanelSlide(panel: View, slideOffset: Float) { @@ -485,6 +511,19 @@ class MainActivity : AppCompatActivity() { Toast.makeText(this, R.string.bookmarked, Toast.LENGTH_SHORT).show() } + private fun showHistoryList() { + binding.historyRecylcleView.visibility = View.VISIBLE + binding.noHistoryText.visibility = + if (historyAdapter.itemCount == 0) View.VISIBLE else View.GONE + binding.bookmarksRecyclerView.visibility = View.GONE + } + + private fun showBookmarksList() { + binding.historyRecylcleView.visibility = View.GONE + binding.noHistoryText.visibility = View.GONE // no “no bookmarks” label yet + binding.bookmarksRecyclerView.visibility = View.VISIBLE + } + fun openAppMenu(view: View) { val popup = PopupMenu(this, view) val inflater = popup.menuInflater diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index b07d38d2d..e2611809a 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -421,16 +421,26 @@ tools:context=".activities.MainActivity" android:background="@drawable/display_background"> + + + app:layout_constraintEnd_toEndOf="parent"/> + app:layout_constraintEnd_toEndOf="parent"/> + + + app:layout_constraintEnd_toEndOf="parent"/> + app:layout_constraintEnd_toEndOf="parent"/> + + app:layout_constraintEnd_toEndOf="parent" /> + app:layout_constraintEnd_toEndOf="parent"/> Nothing to bookmark Bookmarked + Bookmarks From efe8459b9ecc1a18d958d6a37754b12be645d2bd Mon Sep 17 00:00:00 2001 From: SamuelZima Date: Sat, 25 Oct 2025 16:23:16 +0200 Subject: [PATCH 04/12] Fixed history/bookmarks tabs late rendering and UI paddings MainActivity.kt - updated History/Bookmarks tabs setup, so the sliding panel recycler view is always rendered on top of the calculator buttons to resolve the late rendering of calculator buttons on collapsing activity_main.xml for every layout - added umano overlay and updated paddings strings.xml - added string resource for history tab title --- .../opencalculator/activities/MainActivity.kt | 34 +++++++++++-------- .../main/res/layout-land/activity_main.xml | 4 ++- .../res/layout-sw720dp-land/activity_main.xml | 4 ++- app/src/main/res/layout/activity_main.xml | 4 ++- app/src/main/res/values/strings.xml | 3 +- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt index c16a1fb87..39aa20e12 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt @@ -209,29 +209,35 @@ class MainActivity : AppCompatActivity() { // --- END Bookmarks RecyclerView setup --- // --- History/Bookmarks tabs setup --- - binding.historyBookmarksTabs.addTab(binding.historyBookmarksTabs.newTab().setText(R.string.settings_category_history)) + binding.historyBookmarksTabs.addTab(binding.historyBookmarksTabs.newTab().setText(R.string.history_tab_title)) binding.historyBookmarksTabs.addTab(binding.historyBookmarksTabs.newTab().setText(R.string.bookmarks_tab_title)) // Default to History tab showHistoryList() binding.slidingLayout.scrollableView = binding.historyRecylcleView - binding.historyBookmarksTabs.addOnTabSelectedListener(object : com.google.android.material.tabs.TabLayout.OnTabSelectedListener { - override fun onTabSelected(tab: com.google.android.material.tabs.TabLayout.Tab) { - when (tab.position) { - 0 -> { // History - showHistoryList() - binding.slidingLayout.scrollableView = binding.historyRecylcleView - } - 1 -> { // Bookmarks - showBookmarksList() - binding.slidingLayout.scrollableView = binding.bookmarksRecyclerView + binding.historyBookmarksTabs.addOnTabSelectedListener( + object : com.google.android.material.tabs.TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: com.google.android.material.tabs.TabLayout.Tab) { + when (tab.position) { + 0 -> { + showHistoryList() + binding.slidingLayout.scrollableView = binding.historyRecylcleView + } + 1 -> { + showBookmarksList() + binding.slidingLayout.scrollableView = binding.bookmarksRecyclerView + } } + // keep the grab handle above the list and refresh layout + binding.slidingLayoutButton.bringToFront() + binding.constraintLayout3.requestLayout() + binding.slidingLayout.invalidate() } + override fun onTabUnselected(tab: com.google.android.material.tabs.TabLayout.Tab) {} + override fun onTabReselected(tab: com.google.android.material.tabs.TabLayout.Tab) {} } - override fun onTabUnselected(tab: com.google.android.material.tabs.TabLayout.Tab) {} - override fun onTabReselected(tab: com.google.android.material.tabs.TabLayout.Tab) {} - }) + ) // --- END History/Bookmarks tabs setup --- // Set the sliding layout diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index e2611809a..22ed9add9 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -17,6 +17,7 @@ app:layout_constraintTop_toBottomOf="@+id/resultDisplayHorizontalScrollView" app:umanoPanelHeight="31dp" app:umanoShadowHeight="0dp" + app:umanoOverlay="true" tools:ignore="MissingConstraints"> + android:paddingTop="48dp"> @@ -131,7 +132,7 @@ android:layout_width="match_parent" android:layout_height="0dp" android:paddingHorizontal="8dp" - android:paddingTop="12dp" + android:paddingTop="48dp" android:paddingBottom="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent"> @@ -644,6 +645,7 @@ android:id="@+id/bookmarksRecyclerView" android:layout_width="match_parent" android:layout_height="0dp" + android:layout_marginBottom="32dp" android:background="?attr/history_background_color" android:visibility="gone" app:layout_constraintTop_toBottomOf="@+id/historyBookmarksTabs" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e65a07fe5..12901f55b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -175,8 +175,9 @@ Show on Lock Screen Keep the calculator visible on your lock screen for quick and easy access. Disable to hide it when your screen is locked. - + Nothing to bookmark Bookmarked Bookmarks + History From 4b78ffaa84f15179004706b515e69e3abb8cabc5 Mon Sep 17 00:00:00 2001 From: SamuelZima Date: Sat, 25 Oct 2025 17:46:01 +0200 Subject: [PATCH 05/12] Added bookmarking functionality + bookmark icon MainActivity.kt - updated addBookmark() function - added bookmark button setup - added currentCalcAndResult() and updateBookmarkIcon() helpers - added updateBookmarkIcon() function call to every place where the calculation is calculated or cleared activity_main.xml - added Bookmark ImageButton - added two bookmark icons to res/drawable/ --- .../opencalculator/activities/MainActivity.kt | 37 ++++++++++++++++++- app/src/main/res/drawable/ic_bookmark_24.xml | 9 +++++ .../res/drawable/ic_bookmark_border_24.xml | 9 +++++ .../main/res/layout-land/activity_main.xml | 11 ++++++ .../res/layout-sw720dp-land/activity_main.xml | 11 ++++++ app/src/main/res/layout/activity_main.xml | 11 ++++++ app/src/main/res/values/strings.xml | 1 + 7 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_bookmark_24.xml create mode 100644 app/src/main/res/drawable/ic_bookmark_border_24.xml diff --git a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt index 39aa20e12..fe8be339b 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt @@ -127,6 +127,15 @@ class MainActivity : AppCompatActivity() { setContentView(view) + // --- Setup bookmark button --- + binding.bookmarkButton.setOnClickListener { + addBookmark() + } + + // Show correct icon on first render + updateBookmarkIcon() + // --- END Setup bookmark button --- + // Disable the keyboard on display EditText binding.input.showSoftInputOnFocus = false @@ -496,7 +505,7 @@ class MainActivity : AppCompatActivity() { } // Function to bookmark a calculation - fun addBookmark(menuItem: MenuItem) { + fun addBookmark() { val calculation = binding.input.text.toString() val shownResult = binding.resultDisplay.text.toString() @@ -515,8 +524,10 @@ class MainActivity : AppCompatActivity() { binding.bookmarksRecyclerView.scrollToPosition(bookmarksAdapter.itemCount - 1) } Toast.makeText(this, R.string.bookmarked, Toast.LENGTH_SHORT).show() + updateBookmarkIcon() } + // --- History/Bookmarks tabs helpers --- private fun showHistoryList() { binding.historyRecylcleView.visibility = View.VISIBLE binding.noHistoryText.visibility = @@ -529,6 +540,25 @@ class MainActivity : AppCompatActivity() { binding.noHistoryText.visibility = View.GONE // no “no bookmarks” label yet binding.bookmarksRecyclerView.visibility = View.VISIBLE } + // --- END History/Bookmarks tabs helpers --- + + // --- Bookmark helpers --- + private fun currentCalcAndResult(): Pair? { + val calculation = binding.input.text.toString() + val shownResult = binding.resultDisplay.text.toString() + if (calculation.isEmpty() && shownResult.isEmpty()) return null + val result = if (shownResult.isNotEmpty()) shownResult else calculation + return calculation to result + } + + private fun updateBookmarkIcon() { + val pair = currentCalcAndResult() + val isBookmarked = pair?.let { (calc, res) -> MyPreferences(this).isBookmarked(calc, res) } ?: false + val icon = if (isBookmarked) R.drawable.ic_bookmark_24 else R.drawable.ic_bookmark_border_24 + binding.bookmarkButton.setImageResource(icon) + binding.bookmarkButton.isEnabled = pair != null + } + // --- END Bookmark helpers --- fun openAppMenu(view: View) { val popup = PopupMenu(this, view) @@ -786,6 +816,7 @@ class MainActivity : AppCompatActivity() { } else { binding.resultDisplay.text = "" } + updateBookmarkIcon() } // Save to history if the option autoSaveCalculationWithoutEqualButton is enabled @@ -864,12 +895,14 @@ class MainActivity : AppCompatActivity() { } else { withContext(Dispatchers.Main) { binding.resultDisplay.text = "" + updateBookmarkIcon() } } } } else { withContext(Dispatchers.Main) { binding.resultDisplay.text = "" + updateBookmarkIcon() } } } @@ -1129,6 +1162,7 @@ class MainActivity : AppCompatActivity() { binding.input.setText("") binding.resultDisplay.text = "" isStillTheSameCalculation_autoSaveCalculationWithoutEqualOption = false + updateBookmarkIcon() } @SuppressLint("SetTextI18n") @@ -1272,6 +1306,7 @@ class MainActivity : AppCompatActivity() { } else { withContext(Dispatchers.Main) { binding.resultDisplay.text = "" } } + updateBookmarkIcon() } } diff --git a/app/src/main/res/drawable/ic_bookmark_24.xml b/app/src/main/res/drawable/ic_bookmark_24.xml new file mode 100644 index 000000000..b7675c2f1 --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_bookmark_border_24.xml b/app/src/main/res/drawable/ic_bookmark_border_24.xml new file mode 100644 index 000000000..642714877 --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark_border_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 22ed9add9..6b8d0f64b 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -596,6 +596,17 @@ android:textIsSelectable="true" android:textSize="30sp" /> + + diff --git a/app/src/main/res/layout-sw720dp-land/activity_main.xml b/app/src/main/res/layout-sw720dp-land/activity_main.xml index d22e0aa8a..ff7512486 100644 --- a/app/src/main/res/layout-sw720dp-land/activity_main.xml +++ b/app/src/main/res/layout-sw720dp-land/activity_main.xml @@ -107,6 +107,17 @@ android:textIsSelectable="true" android:textSize="45sp" /> + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2ebb27e5b..6dfbdfa1b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -103,6 +103,17 @@ android:textIsSelectable="true" android:textSize="35sp" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 12901f55b..761d181aa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -180,4 +180,5 @@ Bookmarked Bookmarks History + Bookmark From 5e125086ae63eb7158095568676cfceb0e808fa4 Mon Sep 17 00:00:00 2001 From: SamuelZima Date: Sat, 25 Oct 2025 18:52:54 +0200 Subject: [PATCH 06/12] Fixed bookmark button in GUI layouts --- .../main/res/layout-land/activity_main.xml | 33 +++++++++++-------- .../res/layout-sw720dp-land/activity_main.xml | 33 +++++++++++-------- app/src/main/res/layout/activity_main.xml | 33 +++++++++++-------- 3 files changed, 58 insertions(+), 41 deletions(-) diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 6b8d0f64b..1e520a3f9 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -509,6 +509,22 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + app:layout_constraintStart_toEndOf="@+id/bookmarkButton" + app:layout_constraintEnd_toEndOf="parent"> - - diff --git a/app/src/main/res/layout-sw720dp-land/activity_main.xml b/app/src/main/res/layout-sw720dp-land/activity_main.xml index ff7512486..7a7a7f009 100644 --- a/app/src/main/res/layout-sw720dp-land/activity_main.xml +++ b/app/src/main/res/layout-sw720dp-land/activity_main.xml @@ -19,6 +19,22 @@ app:layout_constraintStart_toStartOf="@+id/menuButton" app:layout_constraintTop_toTopOf="parent" /> + + + app:layout_constraintStart_toEndOf="@+id/bookmarkButton" + app:layout_constraintEnd_toEndOf="parent"> - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 6dfbdfa1b..f9cd0652a 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -18,15 +18,33 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + app:layout_constraintStart_toEndOf="@+id/bookmarkButton" + app:layout_constraintEnd_toEndOf="parent"> - - From efcd5dc852d03be6079b9c826d112f9c046ebc0d Mon Sep 17 00:00:00 2001 From: SamuelZima Date: Mon, 27 Oct 2025 09:56:32 +0100 Subject: [PATCH 07/12] Updated MainActivity.kt addBookmark() function addBookmark() -> toggleBookmark() - updated the function to toggle the adding or removing of a bookmark for a calculation by clicking on the bookmark icon --- .../opencalculator/activities/MainActivity.kt | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt index fe8be339b..381e42369 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt @@ -129,7 +129,7 @@ class MainActivity : AppCompatActivity() { // --- Setup bookmark button --- binding.bookmarkButton.setOnClickListener { - addBookmark() + toggleBookmark() } // Show correct icon on first render @@ -504,8 +504,8 @@ class MainActivity : AppCompatActivity() { bookmarksTouchHelper.attachToRecyclerView(binding.bookmarksRecyclerView) } - // Function to bookmark a calculation - fun addBookmark() { + // Function to toggle the adding or removing of a bookmark for a calculation by clicking on the bookmark icon + fun toggleBookmark() { val calculation = binding.input.text.toString() val shownResult = binding.resultDisplay.text.toString() @@ -515,15 +515,29 @@ class MainActivity : AppCompatActivity() { } val result = if (shownResult.isNotEmpty()) shownResult else calculation - val prefs = MyPreferences(this) - val bookmark = prefs.addBookmark(calculation, result) - // Refresh from prefs to reflect dedupe/trim behavior - bookmarksAdapter.updateList(prefs.getBookmarks()) - if (bookmarksAdapter.itemCount > 0) { - binding.bookmarksRecyclerView.scrollToPosition(bookmarksAdapter.itemCount - 1) + + // Toggle: if calculation is already bookmarked, remove it; otherwise add it to bookmarks + if (prefs.isBookmarked(calculation, result)) { + // Find the matching bookmark and remove it by id + val toRemove = prefs.getBookmarks() + .firstOrNull { it.calculation == calculation && it.result == result } + if (toRemove != null) { + prefs.removeBookmarkById(toRemove.id) + } + // Refresh list + bookmarksAdapter.updateList(prefs.getBookmarks()) + } else { + // Bookmark the calculation + prefs.addBookmark(calculation, result) + // Refresh list (reflects dedupe/trim behavior) + bookmarksAdapter.updateList(prefs.getBookmarks()) + if (bookmarksAdapter.itemCount > 0) { + binding.bookmarksRecyclerView.scrollToPosition(bookmarksAdapter.itemCount - 1) + } } - Toast.makeText(this, R.string.bookmarked, Toast.LENGTH_SHORT).show() + + // Update the icon to filled/outlined immediately to show if the calculation is bookmarked or not updateBookmarkIcon() } From 3f77810311e5b5f97516aa918a42b1a30485a054 Mon Sep 17 00:00:00 2001 From: SamuelZima Date: Mon, 27 Oct 2025 10:31:27 +0100 Subject: [PATCH 08/12] Added Clear bookmarks to the app menu MainActivity.kt - added clearBookmarks() function which builds and shows an alert modal and after confirmation deletes all saved bookmarks app_menu.xml - added Clear bookmarks item strings.xml - added strings for the clear bookmarks alert modals --- .../opencalculator/activities/MainActivity.kt | 21 ++++++++++++++++++- app/src/main/res/menu/app_menu.xml | 5 +++++ app/src/main/res/values/strings.xml | 8 ++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt index 381e42369..94e1c0755 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt @@ -32,6 +32,7 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.appcompat.app.AlertDialog import com.darkempire78.opencalculator.MyPreferences import com.darkempire78.opencalculator.R import com.darkempire78.opencalculator.TextSizeAdjuster @@ -50,7 +51,6 @@ import com.darkempire78.opencalculator.databinding.ActivityMainBinding import com.darkempire78.opencalculator.dialogs.DonationDialog import com.darkempire78.opencalculator.history.History import com.darkempire78.opencalculator.history.HistoryAdapter -import com.darkempire78.opencalculator.bookmarks.Bookmark import com.darkempire78.opencalculator.bookmarks.BookmarksAdapter import com.darkempire78.opencalculator.util.ScientificMode import com.darkempire78.opencalculator.util.ScientificModeTypes @@ -494,6 +494,8 @@ class MainActivity : AppCompatActivity() { bookmarksAdapter.removeAt(position) // Persist removal prefs.removeBookmarkById(removed.id) + // Update bookmark icon in case the currently shown calculation is the one that is being deleted + updateBookmarkIcon() } else { // Fallback: refresh from prefs bookmarksAdapter.updateList(prefs.getBookmarks()) @@ -603,6 +605,23 @@ class MainActivity : AppCompatActivity() { checkEmptyHistoryForNoHistoryLabel() } + fun clearBookmarks(menuItem: MenuItem) { + // Build alert modal for action confirmation + AlertDialog.Builder(this) + .setTitle(R.string.confirm_clear_bookmarks_title) + .setPositiveButton(R.string.confirm_clear_bookmarks_button) { _, _ -> + // Clear stored bookmarks + MyPreferences(this).saveBookmarks(emptyList()) + // Refresh the adapter/UI + bookmarksAdapter.updateList(MyPreferences(this).getBookmarks()) + Toast.makeText(this, R.string.bookmarks_cleared, Toast.LENGTH_SHORT).show() + // Also update the bookmark icon state in case the current calculation was bookmarked + updateBookmarkIcon() + } + .setNegativeButton(R.string.cancel_clear_bookmarks_button, null) + .show() + } + private fun keyVibration(view: View) { if (MyPreferences(this).vibrationMode) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { diff --git a/app/src/main/res/menu/app_menu.xml b/app/src/main/res/menu/app_menu.xml index 451d9cbc0..3d1e4de44 100644 --- a/app/src/main/res/menu/app_menu.xml +++ b/app/src/main/res/menu/app_menu.xml @@ -5,6 +5,11 @@ android:title="@string/menu_clear_history" android:onClick="clearHistory" /> + + Nothing to bookmark - Bookmarked Bookmarks History Bookmark + + + Clear bookmarks + Delete all saved bookmarks ? + Bookmarks cleared + Yes + No From 77224dc82d3f670a1dac51245ddeb90577404cca Mon Sep 17 00:00:00 2001 From: SamuelZima Date: Mon, 27 Oct 2025 12:03:12 +0100 Subject: [PATCH 09/12] Fixed bookmarks tab incorrect grouping and weird scrolling BookmarksAdapter.kt - fixed incorrect grouping of calculations based on relative time to look exactly like history MainActivity.kt - sync the scrollableView with the selected tab, so either history or bookmarks recycler view activity_main.xml - updated layouts to fix the weird scrolling on weird collapsing when clicking on the last two elements in the recycler view in the bookmarks tab by using a barrier and updating constraint for the history_sliding_layout_button --- .../opencalculator/activities/MainActivity.kt | 5 ++++- .../bookmarks/BookmarksAdapter.kt | 21 ++++++++++++++++++- .../main/res/layout-land/activity_main.xml | 9 +++++++- .../res/layout-sw720dp-land/activity_main.xml | 9 +++++++- app/src/main/res/layout/activity_main.xml | 9 +++++++- 5 files changed, 48 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt index 94e1c0755..240b0a464 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt @@ -253,7 +253,10 @@ class MainActivity : AppCompatActivity() { binding.slidingLayout.addPanelSlideListener(object : PanelSlideListener { override fun onPanelSlide(panel: View, slideOffset: Float) { if (slideOffset == 0f) { // If the panel got collapsed - binding.slidingLayout.scrollableView = binding.historyRecylcleView + // Keep scrollableView in sync with the selected tab + val isBookmarks = binding.historyBookmarksTabs.selectedTabPosition == 1 + binding.slidingLayout.scrollableView = + if (isBookmarks) binding.bookmarksRecyclerView else binding.historyRecylcleView } } diff --git a/app/src/main/java/com/darkempire78/opencalculator/bookmarks/BookmarksAdapter.kt b/app/src/main/java/com/darkempire78/opencalculator/bookmarks/BookmarksAdapter.kt index a3d6714af..afc12764a 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/bookmarks/BookmarksAdapter.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/bookmarks/BookmarksAdapter.kt @@ -84,7 +84,26 @@ class BookmarksAdapter( DateUtils.FORMAT_ABBREV_RELATIVE ) time.text = rel - // Keep same grouping to look like history for now: + + // Hide current time if previous element is same day + if (position > 0) { + val prev = items[position - 1] + val prevRel = DateUtils.getRelativeTimeSpanString( + prev.time.toLong(), + System.currentTimeMillis(), + DateUtils.DAY_IN_MILLIS, + DateUtils.FORMAT_ABBREV_RELATIVE + ) + if (prevRel == rel) { + time.visibility = View.GONE + } else { + time.visibility = View.VISIBLE + } + } else { + time.visibility = View.VISIBLE + } + + // Hide main separator if next element is same day if (position + 1 < items.size) { val nextRel = DateUtils.getRelativeTimeSpanString( items[position + 1].time.toLong(), diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 1e520a3f9..1b2a0f9e6 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -473,6 +473,13 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> + + + app:layout_constraintTop_toBottomOf="@+id/list_bottom_barrier"/> + + + app:layout_constraintTop_toBottomOf="@+id/list_bottom_barrier"/> + + + app:layout_constraintTop_toBottomOf="@+id/list_bottom_barrier"/> Date: Mon, 27 Oct 2025 14:14:59 +0100 Subject: [PATCH 10/12] Added "No Bookmarks Available" when no bookmarks are saved yet MainActivity.kt - added checkEmptyBookmarksForNoBookmarksLabel() function and updated checkEmptyHistoryForNoHistoryLabel() function to check if we are currently on history or bookmarks tab and if there are no history or bookmark entries - updated showHistoryList() and showBookmarksList() functions - added checkEmptyBookmarksForNoBookmarksLabel() calls after every bookmarks list mutation/deletion activity_main.xml - added TextView element for the no bookmarks text to all three layouts strings.xml - added no bookmarks available string --- .../opencalculator/activities/MainActivity.kt | 27 +++++++++++++++---- .../main/res/layout-land/activity_main.xml | 18 ++++++++++++- .../res/layout-sw720dp-land/activity_main.xml | 18 ++++++++++++- app/src/main/res/layout/activity_main.xml | 18 ++++++++++++- app/src/main/res/values/strings.xml | 1 + 5 files changed, 74 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt index 240b0a464..00f521207 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt @@ -495,6 +495,8 @@ class MainActivity : AppCompatActivity() { val removed = current[position] // Update adapter first for snappy UI bookmarksAdapter.removeAt(position) + // Check if we removed the last bookmark to show the no bookmarks text + checkEmptyBookmarksForNoBookmarksLabel() // Persist removal prefs.removeBookmarkById(removed.id) // Update bookmark icon in case the currently shown calculation is the one that is being deleted @@ -544,20 +546,22 @@ class MainActivity : AppCompatActivity() { // Update the icon to filled/outlined immediately to show if the calculation is bookmarked or not updateBookmarkIcon() + checkEmptyBookmarksForNoBookmarksLabel() } // --- History/Bookmarks tabs helpers --- private fun showHistoryList() { binding.historyRecylcleView.visibility = View.VISIBLE - binding.noHistoryText.visibility = - if (historyAdapter.itemCount == 0) View.VISIBLE else View.GONE binding.bookmarksRecyclerView.visibility = View.GONE + binding.noBookmarksText.visibility = View.GONE + checkEmptyHistoryForNoHistoryLabel() } private fun showBookmarksList() { binding.historyRecylcleView.visibility = View.GONE - binding.noHistoryText.visibility = View.GONE // no “no bookmarks” label yet + binding.noHistoryText.visibility = View.GONE binding.bookmarksRecyclerView.visibility = View.VISIBLE + checkEmptyBookmarksForNoBookmarksLabel() } // --- END History/Bookmarks tabs helpers --- @@ -620,6 +624,7 @@ class MainActivity : AppCompatActivity() { Toast.makeText(this, R.string.bookmarks_cleared, Toast.LENGTH_SHORT).show() // Also update the bookmark icon state in case the current calculation was bookmarked updateBookmarkIcon() + checkEmptyBookmarksForNoBookmarksLabel() } .setNegativeButton(R.string.cancel_clear_bookmarks_button, null) .show() @@ -1588,12 +1593,24 @@ class MainActivity : AppCompatActivity() { } fun checkEmptyHistoryForNoHistoryLabel() { - if (historyAdapter.itemCount==0) { + val isOnHistoryTab = binding.historyBookmarksTabs.selectedTabPosition == 0 + if (isOnHistoryTab && historyAdapter.itemCount==0) { binding.historyRecylcleView.visibility = View.GONE binding.noHistoryText.visibility = View.VISIBLE }else { binding.noHistoryText.visibility = View.GONE - binding.historyRecylcleView.visibility = View.VISIBLE + if (isOnHistoryTab) binding.historyRecylcleView.visibility = View.VISIBLE + } + } + + fun checkEmptyBookmarksForNoBookmarksLabel() { + val isOnBookmarksTab = binding.historyBookmarksTabs.selectedTabPosition == 1 + if (isOnBookmarksTab && bookmarksAdapter.itemCount == 0) { + binding.bookmarksRecyclerView.visibility = View.GONE + binding.noBookmarksText.visibility = View.VISIBLE + } else { + binding.noBookmarksText.visibility = View.GONE + if (isOnBookmarksTab) binding.bookmarksRecyclerView.visibility = View.VISIBLE } } diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 1b2a0f9e6..7a4169335 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -473,12 +473,28 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> + + + app:constraint_referenced_ids="history_recylcle_view,bookmarksRecyclerView,no_history_text,no_bookmarks_text" /> + + + app:constraint_referenced_ids="history_recylcle_view,bookmarksRecyclerView,no_history_text,no_bookmarks_text" /> + + + app:constraint_referenced_ids="history_recylcle_view,bookmarksRecyclerView,no_history_text,no_bookmarks_text" /> Bookmarks History Bookmark + No Bookmarks Available Clear bookmarks From 988229d717c3acf5fcc6ec203509f3624d77f9e4 Mon Sep 17 00:00:00 2001 From: SamuelZima Date: Mon, 27 Oct 2025 15:31:02 +0100 Subject: [PATCH 11/12] Deleted unused functions for bookmarks --- .../darkempire78/opencalculator/MyPreferences.kt | 14 -------------- .../opencalculator/bookmarks/BookmarksAdapter.kt | 15 --------------- 2 files changed, 29 deletions(-) diff --git a/app/src/main/java/com/darkempire78/opencalculator/MyPreferences.kt b/app/src/main/java/com/darkempire78/opencalculator/MyPreferences.kt index 99688a422..0429d650f 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/MyPreferences.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/MyPreferences.kt @@ -175,20 +175,6 @@ class MyPreferences(context: Context) { return bookmark } - fun getBookmarkById(id: String): Bookmark? { - val bookmarks = getBookmarks() - return bookmarks.find { it.id == id } - } - - fun updateBookmarkById(id: String, bookmark: Bookmark) { - val bookmarksList = getBookmarks() - val index = bookmarksList.indexOfFirst { it.id == id } - if (index != -1) { - bookmarksList[index] = bookmark - saveBookmarks(bookmarksList) - } - } - fun removeBookmarkById(id: String) { val list = getBookmarks().filterNot { it.id == id } saveBookmarks(list) diff --git a/app/src/main/java/com/darkempire78/opencalculator/bookmarks/BookmarksAdapter.kt b/app/src/main/java/com/darkempire78/opencalculator/bookmarks/BookmarksAdapter.kt index afc12764a..58e9238a4 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/bookmarks/BookmarksAdapter.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/bookmarks/BookmarksAdapter.kt @@ -39,26 +39,11 @@ class BookmarksAdapter( notifyDataSetChanged() } - fun appendOne(b: Bookmark) { - items.add(b) - if (items.size > 1) { - notifyItemInserted(items.size - 1) - notifyItemRangeChanged(items.size - 2, 2) - } else { - notifyItemInserted(items.size - 1) - } - } - fun removeAt(position: Int) { items.removeAt(position) notifyItemRemoved(position) } - fun clear() { - items.clear() - notifyDataSetChanged() - } - inner class VH(itemView: View) : RecyclerView.ViewHolder(itemView) { private val calculation: TextView = itemView.findViewById(R.id.history_item_calculation) private val result: TextView = itemView.findViewById(R.id.history_item_result) From 63a715c07f66f7ff909722eeeaa051edc1ac367b Mon Sep 17 00:00:00 2001 From: SamuelZima Date: Fri, 31 Oct 2025 10:39:00 +0100 Subject: [PATCH 12/12] Modified swiping functionality in history tab + smaller bookmark icon MainActivity.kt - modified the onSwiped function in setSwipeTouchHelperForRecyclerView(), so that left swipe deletes and right swipe adds history element to bookmarks without deleting it from history strings.xml - added "Added to bookmarks" string to show on history tab after right swipe activity_maina.xml - made the bookmark icon smaller in all layouts --- .../opencalculator/activities/MainActivity.kt | 37 +++++++++++++++++-- .../main/res/layout-land/activity_main.xml | 4 +- .../res/layout-sw720dp-land/activity_main.xml | 4 +- app/src/main/res/layout/activity_main.xml | 4 +- app/src/main/res/values/strings.xml | 1 + 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt index 00f521207..30bef18e1 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt @@ -454,9 +454,40 @@ class MainActivity : AppCompatActivity() { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val position = viewHolder.bindingAdapterPosition - historyAdapter.removeHistoryElement(position) - checkEmptyHistoryForNoHistoryLabel() - deleteElementFromHistory(position) + val prefs = MyPreferences(this@MainActivity) + + if (direction == ItemTouchHelper.RIGHT) { + // Right swipe -> add history element to bookmarks (do NOT delete from history) + val historyList = prefs.getHistory() + if (position in 0 until historyList.size) { + val h = historyList[position] + // Add bookmark, deduplication safe in addBookmark + prefs.addBookmark(h.calculation, h.result) + + // Refresh bookmarks UI and scroll to the bottom for feedback + bookmarksAdapter.updateList(prefs.getBookmarks()) + if (bookmarksAdapter.itemCount > 0) { + binding.bookmarksRecyclerView.scrollToPosition(bookmarksAdapter.itemCount - 1) + } + + // Update bookmark icon in case this calculation is currently shown + updateBookmarkIcon() + + // Restore the swiped row since we don't delete it + historyAdapter.notifyItemChanged(position) + } else { + // Out-of-bounds safety: just refresh the row + historyAdapter.notifyItemChanged(position) + } + + // User feedback + Toast.makeText(this@MainActivity, R.string.bookmarked, Toast.LENGTH_SHORT).show() + } else { + // LEFT swipe -> delete element from history + historyAdapter.removeHistoryElement(position) + checkEmptyHistoryForNoHistoryLabel() + deleteElementFromHistory(position) + } } } diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 7a4169335..018357be6 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -535,10 +535,10 @@ History Bookmark No Bookmarks Available + Added to bookmarks Clear bookmarks