Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/dictionaries/android.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<w>ENOENT</w>
<w>FILESIZE</w>
<w>ONEPLUS</w>
<w>Roborazzi</w>
<w>allempty</w>
<w>apkgfileprovider</w>
<w>asynctasks</w>
Expand Down
23 changes: 23 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.ichi2.anki

import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.KeyEvent
import android.view.Menu
Expand All @@ -30,6 +31,8 @@ import android.view.WindowManager
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.annotation.CheckResult
Expand All @@ -38,7 +41,10 @@ import androidx.annotation.MainThread
import androidx.annotation.VisibleForTesting
import androidx.core.view.MenuHost
import androidx.core.view.MenuProvider
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.commit
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
Expand Down Expand Up @@ -298,6 +304,8 @@ open class CardBrowser :
return
}
tagsDialogFactory = TagsDialogFactory(this).attachToActivity<TagsDialogFactory>(this)
// match the status bar theme of the rest of the app
enableEdgeToEdge(statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT))
super.onCreate(savedInstanceState)
binding = ActivityCardBrowserBinding.inflate(layoutInflater)
if (!ensureStoragePermissions()) {
Expand All @@ -312,6 +320,7 @@ open class CardBrowser :

setViewBinding(binding)
initNavigationDrawer(findViewById(android.R.id.content))
applyToolbarInsets()

/**
* Check if noteEditorFrame is not null and if its visibility is set to VISIBLE.
Expand Down Expand Up @@ -477,6 +486,20 @@ open class CardBrowser :
super.setupBackPressedCallbacks()
}

override fun fitsSystemWindows(): Boolean = false

private fun applyToolbarInsets() {
val container = findViewById<View>(R.id.toolbar_container) ?: return
ViewCompat.setOnApplyWindowInsetsListener(container) { view, insets ->
val bars =
insets.getInsets(
WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.displayCutout(),
)
view.updatePadding(left = bars.left, top = bars.top, right = bars.right)
insets
}
}

private fun showSaveChangesDialog(launcher: NoteEditorLauncher) {
DiscardChangesDialog.showDialog(
context = this,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import androidx.core.view.MenuProvider
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
Expand Down Expand Up @@ -252,7 +253,9 @@ class CardBrowserFragment :
cardsListView =
view.findViewById<RecyclerView>(R.id.card_browser_list).apply {
attachFastScroller(R.id.browser_scroller)
clipToPadding = false
}
applyContentInsets(view)
DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL).apply {
setDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.browser_divider)!!)
cardsListView.addItemDecoration(this)
Expand Down Expand Up @@ -372,6 +375,19 @@ class CardBrowserFragment :
setupMenu()
}

private fun applyContentInsets(root: View) {
ViewCompat.setOnApplyWindowInsetsListener(root) { v, insets ->
val bars =
insets.getInsets(
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout(),
)
v.updatePadding(left = bars.left, right = bars.right)
// RecyclerView uses clipToPadding=false so list scrolls under the navigation bar
cardsListView.updatePadding(bottom = bars.bottom)
insets
}
}

private fun setupMenu() {
val menuHost: MenuHost = requireCardBrowserActivity()

Expand Down
23 changes: 21 additions & 2 deletions AnkiDroid/src/main/java/com/ichi2/themes/Themes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@

package com.ichi2.themes

import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.graphics.Color
import android.util.TypedValue
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.withStyledAttributes
import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.WindowInsetsControllerCompat
Expand All @@ -34,6 +37,7 @@ import com.ichi2.anki.settings.enums.AppTheme
import com.ichi2.anki.settings.enums.DayTheme
import com.ichi2.anki.settings.enums.NightTheme
import com.ichi2.anki.settings.enums.Theme
import com.ichi2.themes.Themes.currentTheme

/**
* Helper methods to configure things related to AnkiDroid's themes
Expand All @@ -50,8 +54,23 @@ object Themes {
context.setTheme(currentTheme.styleResId)
}

fun setLegacyActionBar(context: Context) {
context.setTheme(R.style.ThemeOverlay_LegacyActionBar)
fun setTheme(activity: Activity) {
val tv = TypedValue()
activity.theme.resolveAttribute(android.R.attr.windowBackground, tv, true)
val hadLauncherSplash = tv.resourceId == R.drawable.launch_screen

setTheme(activity as Context)

if (hadLauncherSplash) {
activity.theme.resolveAttribute(android.R.attr.windowBackground, tv, true)
val replacement =
if (tv.type in TypedValue.TYPE_FIRST_COLOR_INT..TypedValue.TYPE_LAST_COLOR_INT) {
tv.data.toDrawable()
} else {
AppCompatResources.getDrawable(activity, tv.resourceId)
}
activity.window.setBackgroundDrawable(replacement)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
android:id="@+id/card_browser_xl_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:orientation="horizontal">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/card_browser_frame"
Expand Down
78 changes: 42 additions & 36 deletions AnkiDroid/src/main/res/layout/include_browser_toolbar.xml
Original file line number Diff line number Diff line change
@@ -1,50 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/toolbar"
android:id="@+id/toolbar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/appBarColor"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ActionBarStyle"
app:navigationContentDescription="@string/abc_action_bar_up_description"
app:navigationIcon="?attr/homeAsUpIndicator">
android:background="?attr/appBarColor">

<com.ichi2.ui.FixedTextView
android:id="@+id/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="@color/white"
android:textSize="20sp"
android:visibility="gone" />

<LinearLayout
android:id="@+id/toolbar_content"
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:orientation="vertical">
android:minHeight="?attr/actionBarSize"
android:theme="@style/ActionBarStyle"
app:navigationContentDescription="@string/abc_action_bar_up_description"
app:navigationIcon="?attr/homeAsUpIndicator">

<com.ichi2.ui.FixedTextView
android:id="@+id/deck_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:drawableEnd="@drawable/id_arrow_drop_down"
android:textAppearance="?attr/textAppearanceListItem"
android:id="@+id/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="@color/white"
android:maxLines="1"
android:ellipsize="end"
tools:text="Deck name here"
/>
android:textSize="20sp"
android:visibility="gone" />

<TextView
android:id="@+id/subtitle"
<LinearLayout
android:id="@+id/toolbar_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textAppearance="?attr/textAppearanceListItemSecondary"
tools:text="2 cards shown"/>
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
android:background="?attr/selectableItemBackground"
android:orientation="vertical">

<com.ichi2.ui.FixedTextView
android:id="@+id/deck_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:drawableEnd="@drawable/id_arrow_drop_down"
android:textAppearance="?attr/textAppearanceListItem"
android:textColor="@color/white"
android:maxLines="1"
android:ellipsize="end"
tools:text="Deck name here"
/>

<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textAppearance="?attr/textAppearanceListItemSecondary"
tools:text="2 cards shown"/>
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
</FrameLayout>
1 change: 0 additions & 1 deletion AnkiDroid/src/main/res/layout/item_card_browser.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
android:id="@+id/card_item_browser"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
xmlns:tools="http://schemas.android.com/tools">

<CheckBox
Expand Down
7 changes: 0 additions & 7 deletions AnkiDroid/src/main/res/values/legacy_action_bar.xml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2026 David Allison <davidallisongithub@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.ichi2.anki

import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith

/**
* Screenshot tests for [CardBrowser]
*
* `./gradlew :AnkiDroid:verifyRoborazziPlayDebug -Pscreenshot --tests "com.ichi2.anki.CardBrowserScreenshotTest"`
*/
@RunWith(AndroidJUnit4::class)
class CardBrowserScreenshotTest : ScreenshotTest() {
init {
setPhoneQualifiers()
}

@Test
fun cardBrowserWith30Notes() =
withCardBrowser(noteCount = 50) { browser ->
// Robolectric reports zero system-bar insets by default. Inject realistic ones
// so the app's edge-to-edge layout responds as it would on a real device.
val density = browser.resources.displayMetrics.density
val statusBarPx = (24 * density).toInt()
val navBarPx = (48 * density).toInt()
val insets =
WindowInsetsCompat
.Builder()
.setInsets(WindowInsetsCompat.Type.statusBars(), Insets.of(0, statusBarPx, 0, 0))
.setInsets(WindowInsetsCompat.Type.navigationBars(), Insets.of(0, 0, 0, navBarPx))
.build()
ViewCompat.dispatchApplyWindowInsets(browser.window.decorView, insets)

// overlay a translucent band where the nav bar would sit
// to see if content is drawn underneath it
val decor = browser.window.decorView as ViewGroup
val navBarOverlay =
View(browser).apply {
setBackgroundColor(0x80000000.toInt())
}
decor.addView(
navBarOverlay,
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
navBarPx,
Gravity.BOTTOM,
),
)

captureScreen("30_notes")
}
}
Loading
Loading