Skip to content

Commit

Permalink
Merge branch 'main' into my-feature-branch
Browse files Browse the repository at this point in the history
  • Loading branch information
jainv4156 authored Oct 10, 2024
2 parents 3c56315 + d189482 commit 33ba166
Show file tree
Hide file tree
Showing 956 changed files with 7,904 additions and 4,370 deletions.
13 changes: 6 additions & 7 deletions .github/workflows/label.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,12 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `Message to maintainers, this PR contains strings changes.
1. Before merging this PR, it is best to run the "Sync Translations" GitHub action, then make and merge a PR from the i18n_sync branch to get translations cleaned out.
2. Then merge this PR, and immediately do another translation PR so the huge change made by this PR's key changes are all by themselves.
Read more about updating strings on the wiki,
- [localization-administration](https://github.com/ankidroid/Anki-Android/wiki/Development-Guide#localization-administration)
- [download-localized-strings](https://github.com/ankidroid/Anki-Android/wiki/Development-Guide#download-localized-strings)`
body: `> [!IMPORTANT]
> **Maintainers**: This PR contains https://github.com/ankidroid/Anki-Android/labels/Strings changes
1. [Sync Translations](https://github.com/ankidroid/Anki-Android/actions/workflows/sync_translations.yml) before merging this PR and wait for the action to complete
2. Review and merge the [auto-generated PR](https://github.com/ankidroid/Anki-Android/pulls/mikehardy-machineaccount) in order to sync all user-submitted translations
3. [Sync Translations again](https://github.com/ankidroid/Anki-Android/actions/workflows/sync_translations.yml) and merge the [PR](https://github.com/ankidroid/Anki-Android/pulls/mikehardy-machineaccount) so the huge automated string changes caused by merging this PR are by themselves and easy to review`
})
}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/opencollective_notices.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ jobs:
// Incidentally you cannot use arry.forEach() with async functions; it does not await. for or for...in is required.
for (const key of Object.keys(uniqueContributorsWithPR)) {
// skip some users, we don't allow bots to claim for their work, yet?
if (key === 'github-actions' || key.includes('dependabot') || key.includes('[bot]')) {
if (key === 'github-actions' || key.includes('dependabot') || key.includes('[bot]' || key.includes('machineaccount')) {
console.log(`ignoring pr with bot user ${key}`);
continue;
}
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/sync_translations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@ name: Sync Translations
on:
workflow_dispatch:

permissions:
contents: read
# We use a machine account PAT from secrets so workflows are triggered
# the default token is not needed and should be fully restricted
permissions: {}

jobs:
sync_translations:
permissions:
contents: write # for Git to git push
pull-requests: write # to create the PR with changes
name: 'Sync Translations with Crowdin'
timeout-minutes: 20
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.MACHINE_ACCOUNT_PAT }}
ref: 'main'
fetch-depth: 0

Expand Down Expand Up @@ -86,6 +85,7 @@ jobs:
if: ${{ steps.tr_check.outputs.HAS_CHANGES }}
uses: actions/github-script@v7
with:
github-token: ${{ secrets.MACHINE_ACCOUNT_PAT }}
script: |
const now = new Date();
// Date format used: YYYY/MM/DD HH:MM , UTC time(ex: 2023/01/13 08:38)
Expand Down
4 changes: 2 additions & 2 deletions AnkiDroid/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ android {
//
// This ensures the correct ordering between the various types of releases (dev < alpha < beta < release) which is
// needed for upgrades to be offered correctly.
versionCode=21900202
versionName="2.19beta2"
versionCode=21900204
versionName="2.19beta4"
minSdk libs.versions.minSdk.get().toInteger()

// Stays until this is in a release: https://github.com/google/desugar_jdk_libs/commit/c01a5446ca13586b801dbba4d83c6821337b3cc2
Expand Down
3 changes: 3 additions & 0 deletions AnkiDroid/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
# debugging stack traces.
-keepattributes SourceFile,LineNumberTable

# We do not have commercial interests to protect, so optimize for easier debugging
-dontobfuscate

# Used through Reflection
-keep class com.ichi2.anki.**.*Fragment { *; }
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
Expand Down
4 changes: 2 additions & 2 deletions AnkiDroid/robolectricDownloader.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def robolectricAndroidSdkVersions = [
[androidVersion: "11", frameworkSdkBuildVersion: "6757853"],
// [androidVersion: "12", frameworkSdkBuildVersion: "7732740"],
// [androidVersion: "12.1", frameworkSdkBuildVersion: "8229987"],
[androidVersion: "13", frameworkSdkBuildVersion: "9030017"],
// [androidVersion: "13", frameworkSdkBuildVersion: "9030017"],
[androidVersion: "14", frameworkSdkBuildVersion: "10818077"],
]

Expand All @@ -42,7 +42,7 @@ tasks.register('robolectricSdkDownload') {

// Generate the configuration and actual copy tasks.
robolectricAndroidSdkVersions.forEach { robolectricSdkVersion ->
def version = "${robolectricSdkVersion['androidVersion']}-robolectric-${robolectricSdkVersion['frameworkSdkBuildVersion']}-i4"
def version = "${robolectricSdkVersion['androidVersion']}-robolectric-${robolectricSdkVersion['frameworkSdkBuildVersion']}-i6"

// Creating a configuration with a dependency allows Gradle to manage the actual resolution of
// the jar file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ package com.ichi2.anki
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.res.Configuration
import android.graphics.Bitmap
Expand Down Expand Up @@ -72,7 +70,6 @@ import androidx.annotation.CheckResult
import androidx.annotation.IdRes
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.children
Expand Down Expand Up @@ -110,7 +107,6 @@ import com.ichi2.anki.pages.AnkiServer
import com.ichi2.anki.pages.CongratsPage
import com.ichi2.anki.pages.PostRequestHandler
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.receiver.SdCardReceiver
import com.ichi2.anki.reviewer.AutomaticAnswer
import com.ichi2.anki.reviewer.AutomaticAnswer.AutomaticallyAnswered
import com.ichi2.anki.reviewer.AutomaticAnswerAction
Expand All @@ -129,7 +125,6 @@ import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.anki.utils.OnlyOnce.Method.ANSWER_CARD
import com.ichi2.anki.utils.OnlyOnce.preventSimultaneousExecutions
import com.ichi2.annotations.NeedsTest
import com.ichi2.compat.CompatHelper.Companion.registerReceiverCompat
import com.ichi2.compat.CompatHelper.Companion.resolveActivityCompat
import com.ichi2.compat.ResolveInfoFlagsCompat
import com.ichi2.libanki.Card
Expand Down Expand Up @@ -192,10 +187,6 @@ abstract class AbstractFlashcardViewer :
@VisibleForTesting
val jsApi by lazy { AnkiDroidJsAPI(this) }

/**
* Broadcast that informs us when the sd card is about to be unmounted
*/
private var unmountReceiver: BroadcastReceiver? = null
private var tagsDialogFactory: TagsDialogFactory? = null

/**
Expand Down Expand Up @@ -590,7 +581,7 @@ abstract class AbstractFlashcardViewer :
super.onCollectionLoaded(col)
val mediaDir = col.media.dir
cardMediaPlayer = CardMediaPlayer.newInstance(this, getMediaBaseUrl(mediaDir))
registerExternalStorageListener()
registerReceiver()
restoreCollectionPreferences(col)
initLayout()
cardRenderContext = createInstance(this, col, typeAnswer!!)
Expand Down Expand Up @@ -653,9 +644,6 @@ abstract class AbstractFlashcardViewer :
override fun onDestroy() {
super.onDestroy()
tts.releaseTts(this)
if (unmountReceiver != null) {
unregisterReceiver(unmountReceiver)
}
// WebView.destroy() should be called after the end of use
// http://developer.android.com/reference/android/webkit/WebView.html#destroy()
if (cardFrame != null) {
Expand Down Expand Up @@ -805,24 +793,6 @@ abstract class AbstractFlashcardViewer :
return cardContent != null
}

/**
* Show/dismiss dialog when sd card is ejected/remounted (collection is saved by SdCardReceiver)
*/
private fun registerExternalStorageListener() {
if (unmountReceiver == null) {
unmountReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == SdCardReceiver.MEDIA_EJECT) {
finish()
}
}
}
val iFilter = IntentFilter()
iFilter.addAction(SdCardReceiver.MEDIA_EJECT)
registerReceiverCompat(unmountReceiver, iFilter, ContextCompat.RECEIVER_EXPORTED)
}
}

open fun undo(): Job {
return launchCatchingTask {
undoAndShowSnackbar(duration = Reviewer.ACTION_SNACKBAR_TIME)
Expand Down
97 changes: 96 additions & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,23 @@ package com.ichi2.anki
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.BitmapFactory
import android.graphics.Color
import android.media.AudioManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import android.view.animation.Animation
import android.widget.ProgressBar
Expand All @@ -35,10 +42,12 @@ import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_LIGHT
import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_SYSTEM
import androidx.core.app.NotificationCompat
import androidx.core.app.PendingIntentCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import com.google.android.material.color.MaterialColors
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anim.ActivityTransitionAnimation
import com.ichi2.anim.ActivityTransitionAnimation.Direction
import com.ichi2.anim.ActivityTransitionAnimation.Direction.DEFAULT
Expand All @@ -51,9 +60,14 @@ import com.ichi2.anki.dialogs.SimpleMessageDialog.SimpleMessageDialogListener
import com.ichi2.anki.preferences.Preferences
import com.ichi2.anki.preferences.Preferences.Companion.MINIMUM_CARDS_DUE_FOR_NOTIFICATION
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.receiver.SdCardReceiver
import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.anki.workarounds.AppLoadedFromBackupWorkaround.showedActivityFailedScreen
import com.ichi2.async.CollectionLoader
import com.ichi2.compat.CompatHelper
import com.ichi2.compat.CompatHelper.Companion.registerReceiverCompat
import com.ichi2.compat.CompatV24
import com.ichi2.compat.ShortcutGroupProvider
import com.ichi2.compat.customtabs.CustomTabActivityHelper
import com.ichi2.compat.customtabs.CustomTabsFallback
import com.ichi2.compat.customtabs.CustomTabsHelper
Expand All @@ -66,11 +80,20 @@ import androidx.browser.customtabs.CustomTabsIntent.Builder as CustomTabsIntentB

@UiThread
@KotlinCleanup("set activityName")
open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener {
open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener, ShortcutGroupProvider, AnkiActivityProvider {

/**
* Receiver that informs us when a broadcast listen in [broadcastsActions] is received.
*
* @see registerReceiver
* @see broadcastsActions
*/
private var broadcastReceiver: BroadcastReceiver? = null

/** The name of the parent class (example: 'Reviewer') */
private val activityName: String
val dialogHandler = DialogHandler(this)
override val ankiActivity = this

private val customTabActivityHelper: CustomTabActivityHelper = CustomTabActivityHelper()

Expand Down Expand Up @@ -112,6 +135,11 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener {
customTabActivityHelper.unbindCustomTabsService(this)
}

override fun onDestroy() {
super.onDestroy()
broadcastReceiver?.let { unregisterReceiver(it) }
}

override fun onResume() {
super.onResume()
UsageAnalytics.sendAnalyticsScreenView(this)
Expand Down Expand Up @@ -150,6 +178,40 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener {
hideProgressBar()
}

/**
* Maps from intent name action to function to run when this action is received by [broadcastReceiver].
* By default it handles [SdCardReceiver.MEDIA_EJECT], and shows/dismisses dialogs when an SD
* card is ejected/remounted (collection is saved beforehand by [SdCardReceiver])
*/
protected open val broadcastsActions = mapOf(
SdCardReceiver.MEDIA_EJECT to { onSdCardNotMounted() }
)

/**
* Register a broadcast receiver, associating an intent to an action as in [broadcastsActions].
* Add more values in [broadcastsActions] to react to more intents.
*/
fun registerReceiver() {
if (broadcastReceiver != null) {
// Receiver already registered
return
}
broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
broadcastsActions[intent.action]?.invoke()
}
}.also {
val iFilter = IntentFilter()
broadcastsActions.keys.map(iFilter::addAction)
registerReceiverCompat(it, iFilter, ContextCompat.RECEIVER_EXPORTED)
}
}

protected fun onSdCardNotMounted() {
showThemedToast(this, resources.getString(R.string.sd_card_not_mounted), false)
finish()
}

/** Legacy code should migrate away from this, and use withCol {} instead.
* */
val getColUnsafe: Collection
Expand Down Expand Up @@ -570,6 +632,32 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener {
finish()
}

override fun onProvideKeyboardShortcuts(
data: MutableList<KeyboardShortcutGroup>,
menu: Menu?,
deviceId: Int
) {
val shortcutGroups = CompatHelper.compat.getShortcuts(this)
data.addAll(shortcutGroups)
super.onProvideKeyboardShortcuts(data, menu, deviceId)
}

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
if (event.isAltPressed && keyCode == KeyEvent.KEYCODE_K) {
CompatHelper.compat.showKeyboardShortcutsDialog(this)
return true
}

val done = super.onKeyUp(keyCode, event)

// Show snackbar only if the current activity have shortcuts, a modifier key is pressed and the keyCode is an unmapped alphabet key
if (!done && shortcuts != null && (event.isCtrlPressed || event.isAltPressed || event.isMetaPressed) && (keyCode in KeyEvent.KEYCODE_A..KeyEvent.KEYCODE_Z) || (keyCode in KeyEvent.KEYCODE_NUMPAD_0..KeyEvent.KEYCODE_NUMPAD_9)) {
showSnackbar(R.string.show_shortcuts_message, Snackbar.LENGTH_SHORT)
return true
}
return false
}

/**
* If storage permissions are not granted, shows a toast message and finishes the activity.
*
Expand All @@ -587,6 +675,9 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener {
return false
}

override val shortcuts
get(): CompatV24.ShortcutGroup? = null

companion object {
const val DIALOG_FRAGMENT_TAG = "dialog"

Expand Down Expand Up @@ -620,3 +711,7 @@ fun Fragment.requireAnkiActivity(): AnkiActivity {
return requireActivity() as? AnkiActivity?
?: throw java.lang.IllegalStateException("Fragment $this not attached to an AnkiActivity.")
}

interface AnkiActivityProvider {
val ankiActivity: AnkiActivity
}
2 changes: 1 addition & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ open class AnkiDroidApp : Application(), Configuration.Provider, ChangeManager.S
LanguageUtil.setDefaultBackendLanguages()

// Create the AnkiDroid directory if missing. Send exception report if inaccessible.
if (Permissions.hasStorageAccessPermission(this)) {
if (Permissions.hasLegacyStorageAccessPermission(this)) {
try {
val dir = CollectionHelper.getCurrentAnkiDroidDirectory(this)
CollectionHelper.initializeAnkiDroidDirectory(dir)
Expand Down
Loading

0 comments on commit 33ba166

Please sign in to comment.