Merge remote-tracking branch 'origin/master' into feature/edit_messages

This commit is contained in:
Marcel Hibbe 2024-02-16 17:09:03 +01:00
commit 9a4bf14e09
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
15 changed files with 262 additions and 49 deletions

View File

@ -32,7 +32,7 @@ jobs:
with:
swap-size-gb: 10
- name: Initialize CodeQL
uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0
uses: github/codeql-action/init@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3
with:
languages: ${{ matrix.language }}
- name: Set up JDK 17
@ -46,4 +46,4 @@ jobs:
echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > "$HOME/.gradle/gradle.properties"
./gradlew assembleDebug
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0
uses: github/codeql-action/analyze@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3

View File

@ -19,4 +19,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: gradle/wrapper-validation-action@27152f6fa06a6b8062ef7195c795692e51fc2c81 # v2.0.0
- uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2 # v2.1.1

View File

@ -37,6 +37,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0
uses: github/codeql-action/upload-sarif@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3
with:
sarif_file: results.sarif

View File

@ -25,7 +25,7 @@ jobs:
distribution: "temurin"
java-version: 17
- name: Run unit tests with coverage
uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0
uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0
with:
arguments: testGplayDebugUnit
- name: Upload test artifacts

View File

@ -48,8 +48,8 @@ android {
// mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable)
// xx .xxx .xx .xx
versionCode 180010010
versionName "18.1.0 Alpha 10"
versionCode 190000001
versionName "19.0.0 Alpha 01"
flavorDimensions "default"
renderscriptTargetApi 19
@ -247,7 +247,7 @@ dependencies {
implementation 'com.github.wooplr:Spotlight:1.3'
implementation 'com.google.code.findbugs:jsr305:3.0.2'
implementation 'com.github.nextcloud-deps:ChatKit:0.4.2'
implementation 'joda-time:joda-time:2.12.6'
implementation 'joda-time:joda-time:2.12.7'
implementation "io.coil-kt:coil:${coilKtVersion}"
implementation "io.coil-kt:coil-gif:${coilKtVersion}"
implementation "io.coil-kt:coil-svg:${coilKtVersion}"
@ -308,11 +308,11 @@ dependencies {
spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.4'
gplayImplementation 'com.google.android.gms:play-services-base:18.3.0'
gplayImplementation "com.google.firebase:firebase-messaging:23.4.0"
gplayImplementation "com.google.firebase:firebase-messaging:23.4.1"
implementation 'androidx.activity:activity-ktx:1.8.2'
implementation 'com.github.nextcloud.android-common:ui:0.14.0'
implementation 'com.github.nextcloud.android-common:ui:0.15.0'
implementation 'com.github.nextcloud-deps:android-talk-webrtc:110.5481.0'
}

View File

@ -23,8 +23,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<application
android:name=".application.NextcloudTalkApplication"
android:allowBackup="true"

View File

@ -1117,7 +1117,7 @@ class CallActivity : CallBaseActivity() {
micInputAudioRecorder.read(byteArr, 0, byteArr.size)
val isCurrentlySpeaking = abs(byteArr[0].toDouble()) > MICROPHONE_VALUE_THRESHOLD
if (isCurrentlySpeaking && !isSpeakingLongTerm) {
if (microphoneOn && isCurrentlySpeaking && !isSpeakingLongTerm) {
isSpeakingLongTerm = true
sendIsSpeakingMessage(true)
} else if (!isCurrentlySpeaking && isSpeakingLongTerm) {

View File

@ -31,10 +31,12 @@ package com.nextcloud.talk.chat
import android.Manifest
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.res.AssetFileDescriptor
import android.content.res.Resources
@ -42,7 +44,9 @@ import android.database.Cursor
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.media.AudioFocusRequest
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioRecord
import android.media.MediaPlayer
import android.media.MediaRecorder
@ -389,6 +393,20 @@ class ChatActivity :
private var lastRecordMediaPosition: Int = 0
private var lastRecordedSeeked: Boolean = false
private val audioFocusChangeListener = getAudioFocusChangeListener()
private val noisyAudioStreamReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
chatViewModel.isPausedDueToBecomingNoisy = true
if (isVoicePreviewPlaying) {
pausePreviewVoicePlaying()
}
if (currentlyPlayedVoiceMessage != null) {
pausePlayback(currentlyPlayedVoiceMessage!!)
}
}
}
private lateinit var participantPermissions: ParticipantPermissions
private var videoURI: Uri? = null
@ -1217,7 +1235,9 @@ class ChatActivity :
binding.messageInputView.micInputCloud.setOnClickListener {
if (mediaRecorderState == MediaRecorderState.RECORDING) {
recorder?.stop()
audioFocusRequest(false) {
recorder?.stop()
}
mediaRecorderState = MediaRecorderState.INITIAL
stopMicInputRecordingAnimation()
showPreviewVoiceRecording(true)
@ -1446,8 +1466,11 @@ class ChatActivity :
duration = it.duration.toLong()
interpolator = LinearInterpolator()
}
voicePreviewMediaPlayer!!.start()
voicePreviewObjectAnimator!!.start()
audioFocusRequest(true) {
voicePreviewMediaPlayer!!.start()
voicePreviewObjectAnimator!!.start()
handleBecomingNoisyBroadcast(register = true)
}
}
setOnCompletionListener {
@ -1461,15 +1484,21 @@ class ChatActivity :
if (voicePreviewMediaPlayer == null) {
initPreviewVoiceRecording()
} else {
voicePreviewMediaPlayer!!.start()
voicePreviewObjectAnimator!!.resume()
audioFocusRequest(true) {
voicePreviewMediaPlayer!!.start()
voicePreviewObjectAnimator!!.resume()
handleBecomingNoisyBroadcast(register = true)
}
}
}
private fun pausePreviewVoicePlaying() {
Log.d(TAG, "paused preview voice recording")
voicePreviewMediaPlayer!!.pause()
voicePreviewObjectAnimator!!.pause()
audioFocusRequest(false) {
voicePreviewMediaPlayer!!.pause()
voicePreviewObjectAnimator!!.pause()
handleBecomingNoisyBroadcast(register = false)
}
}
private fun stopPreviewVoicePlaying() {
@ -1479,9 +1508,12 @@ class ChatActivity :
voicePreviewObjectAnimator!!.end()
voicePreviewObjectAnimator = null
binding.messageInputView.seekBar.clearAnimation()
voicePreviewMediaPlayer!!.stop()
voicePreviewMediaPlayer!!.release()
voicePreviewMediaPlayer = null
audioFocusRequest(false) {
voicePreviewMediaPlayer!!.stop()
voicePreviewMediaPlayer!!.release()
voicePreviewMediaPlayer = null
handleBecomingNoisyBroadcast(register = false)
}
}
}
@ -1935,6 +1967,73 @@ class ChatActivity :
}
}
private fun getAudioFocusChangeListener(): AudioManager.OnAudioFocusChangeListener {
return AudioManager.OnAudioFocusChangeListener { flag ->
when (flag) {
AudioManager.AUDIOFOCUS_LOSS -> {
chatViewModel.isPausedDueToBecomingNoisy = false
if (isVoicePreviewPlaying) {
stopPreviewVoicePlaying()
}
if (currentlyPlayedVoiceMessage != null) {
stopMediaPlayer(currentlyPlayedVoiceMessage!!)
}
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
chatViewModel.isPausedDueToBecomingNoisy = false
if (isVoicePreviewPlaying) {
pausePreviewVoicePlaying()
}
if (currentlyPlayedVoiceMessage != null) {
pausePlayback(currentlyPlayedVoiceMessage!!)
}
}
}
}
}
private fun audioFocusRequest(shouldRequestFocus: Boolean, onGranted: () -> Unit) {
if (chatViewModel.isPausedDueToBecomingNoisy) {
onGranted()
return
}
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
val duration = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
val isGranted: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val focusRequest = AudioFocusRequest.Builder(duration)
.setOnAudioFocusChangeListener(audioFocusChangeListener)
.build()
if (shouldRequestFocus) {
audioManager.requestAudioFocus(focusRequest)
} else {
audioManager.abandonAudioFocusRequest(focusRequest)
}
} else {
@Deprecated("This method was deprecated in API level 26.")
if (shouldRequestFocus) {
audioManager.requestAudioFocus(audioFocusChangeListener, AudioManager.STREAM_MUSIC, duration)
} else {
audioManager.abandonAudioFocus(audioFocusChangeListener)
}
}
if (isGranted == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
onGranted()
}
}
private fun handleBecomingNoisyBroadcast(register: Boolean) {
if (register && !chatViewModel.receiverRegistered) {
registerReceiver(noisyAudioStreamReceiver, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY))
chatViewModel.receiverRegistered = true
} else if (!chatViewModel.receiverUnregistered) {
unregisterReceiver(noisyAudioStreamReceiver)
chatViewModel.receiverUnregistered = true
chatViewModel.receiverRegistered = false
}
}
private fun startPlayback(message: ChatMessage) {
if (!active) {
// don't begin to play voice message if screen is not visible anymore.
@ -1948,8 +2047,10 @@ class ChatActivity :
mediaPlayer?.let {
if (!it.isPlaying) {
it.start()
Log.d(TAG, "MediaPlayer has Started")
audioFocusRequest(true) {
it.start()
handleBecomingNoisyBroadcast(register = true)
}
}
mediaPlayerHandler = Handler()
@ -1984,8 +2085,10 @@ class ChatActivity :
private fun pausePlayback(message: ChatMessage) {
if (mediaPlayer!!.isPlaying) {
mediaPlayer!!.pause()
Log.d(TAG, "MediaPlayer is paused")
audioFocusRequest(false) {
mediaPlayer!!.pause()
handleBecomingNoisyBroadcast(register = false)
}
}
message.isPlayingVoiceMessage = false
@ -2043,7 +2146,10 @@ class ChatActivity :
mediaPlayer?.let {
if (it.isPlaying) {
Log.d(TAG, "media player is stopped")
it.stop()
audioFocusRequest(false) {
it.stop()
handleBecomingNoisyBroadcast(register = false)
}
}
}
} catch (e: IllegalStateException) {
@ -2259,8 +2365,10 @@ class ChatActivity :
private fun stopMicInputRecordingAnimation() {
if (micInputAudioRecordThread != null) {
Log.d(TAG, "Mic Animation Ended")
micInputAudioRecorder.stop()
micInputAudioRecorder.release()
audioFocusRequest(false) {
micInputAudioRecorder.stop()
micInputAudioRecorder.release()
}
isMicInputAudioThreadRunning = false
micInputAudioRecordThread = null
}
@ -2311,7 +2419,9 @@ class ChatActivity :
}
try {
start()
audioFocusRequest(true) {
start()
}
mediaRecorderState = MediaRecorderState.RECORDING
Log.d(TAG, "recording started")
} catch (e: IllegalStateException) {
@ -2352,8 +2462,10 @@ class ChatActivity :
recorder?.apply {
try {
if (mediaRecorderState == MediaRecorderState.RECORDING) {
stop()
reset()
audioFocusRequest(false) {
stop()
reset()
}
mediaRecorderState = MediaRecorderState.INITIAL
Log.d(TAG, "stopped recorder")
}

View File

@ -48,6 +48,11 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository)
open class GetReminderExistState(val reminder: Reminder) : ViewState
private val _getReminderExistState: MutableLiveData<ViewState> = MutableLiveData(GetReminderStartState)
var isPausedDueToBecomingNoisy = false
var receiverRegistered = false
var receiverUnregistered = false
val getReminderExistState: LiveData<ViewState>
get() = _getReminderExistState

View File

@ -44,6 +44,7 @@ import androidx.core.view.marginBottom
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.fragment.app.DialogFragment
import androidx.media3.common.AudioAttributes
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
@ -165,7 +166,10 @@ class FullScreenMediaActivity : AppCompatActivity() {
}
private fun initializePlayer() {
player = ExoPlayer.Builder(applicationContext).build()
player = ExoPlayer.Builder(applicationContext)
.setAudioAttributes(AudioAttributes.DEFAULT, true)
.setHandleAudioBecomingNoisy(true)
.build()
binding.playerView.player = player
}
@ -202,6 +206,7 @@ class FullScreenMediaActivity : AppCompatActivity() {
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
supportActionBar?.show()
}
private fun applyWindowInsets() {
val playerView = binding.playerView
val exoControls = playerView.findViewById<FrameLayout>(R.id.exo_bottom_bar)

View File

@ -2,7 +2,9 @@
* Nextcloud Talk application
*
* @author Marcel Hibbe
* @author Parneet Singh
* Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2024-2025 Parneet Singh <gurayaparneet@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
@ -22,7 +24,6 @@ package com.nextcloud.talk.jobs
import android.Manifest
import android.app.Activity
import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
@ -86,7 +87,6 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
private var mNotifyManager: NotificationManager? = null
private var mBuilder: NotificationCompat.Builder? = null
private lateinit var notification: Notification
private var notificationId: Int = 0
lateinit var roomToken: String
@ -168,7 +168,7 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
}
if (uploadSuccess) {
mNotifyManager?.cancel(notificationId)
cancelNotification()
return Result.success()
} else if (isStopped) {
// since work is cancelled the result would be ignored anyways
@ -196,12 +196,12 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
}
override fun onTransferProgress(percentage: Int) {
notification = mBuilder!!
val progressUpdateNotification = mBuilder!!
.setProgress(HUNDRED_PERCENT, percentage, false)
.setContentText(getNotificationContentText(percentage))
.build()
mNotifyManager!!.notify(notificationId, notification)
mNotifyManager!!.notify(notificationId, progressUpdateNotification)
}
override fun onStopped() {
@ -223,22 +223,60 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
}
private fun initNotificationWithPercentage() {
notification = mBuilder!!
.setContentTitle(getResourceString(context, R.string.nc_upload_in_progess))
val initNotification = mBuilder!!
.setContentTitle(context.resources.getString(R.string.nc_upload_in_progess))
.setContentText(getNotificationContentText(ZERO_PERCENT))
.setSmallIcon(R.drawable.upload_white)
.setOngoing(true)
.setProgress(HUNDRED_PERCENT, ZERO_PERCENT, false)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setGroup(NotificationUtils.KEY_UPLOAD_GROUP)
.setContentIntent(getIntentToOpenConversation())
.addAction(
R.drawable.ic_cancel_white_24dp, getResourceString(context, R.string.nc_cancel),
R.drawable.ic_cancel_white_24dp,
getResourceString(context, R.string.nc_cancel),
getCancelUploadIntent()
)
.build()
notificationId = SystemClock.uptimeMillis().toInt()
mNotifyManager!!.notify(notificationId, notification)
mNotifyManager!!.notify(notificationId, initNotification)
// only need one summary notification but multiple upload worker can call it more than once but it is safe
// because of the same notification object config and id.
makeSummaryNotification()
}
private fun makeSummaryNotification() {
// summary notification encapsulating the group of notifications
val summaryNotification = NotificationCompat.Builder(
context,
NotificationUtils.NotificationChannels
.NOTIFICATION_CHANNEL_UPLOADS.name
).setSmallIcon(R.drawable.upload_white)
.setGroup(NotificationUtils.KEY_UPLOAD_GROUP)
.setGroupSummary(true)
.build()
mNotifyManager?.notify(NotificationUtils.GROUP_SUMMARY_NOTIFICATION_ID, summaryNotification)
}
private fun getActiveUploadNotifications(): Int? {
// filter out active notifications that are upload notifications using group
return mNotifyManager?.activeNotifications?.filter {
it.notification.group == NotificationUtils
.KEY_UPLOAD_GROUP
}?.size
}
private fun cancelNotification() {
mNotifyManager?.cancel(notificationId)
// summary notification would not get dismissed automatically
// if child notifications are cancelled programmatically
// so check if only 1 notification left if yes
// then cancel it (which is summary notification)
if (getActiveUploadNotifications() == 1) {
mNotifyManager?.cancel(NotificationUtils.GROUP_SUMMARY_NOTIFICATION_ID)
}
}
private fun getNotificationContentText(percentage: Int): String {
@ -289,17 +327,21 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
getResourceString(context, R.string.nc_upload_failed_notification_text),
fileName
)
notification = mBuilder!!
val failureNotification = NotificationCompat.Builder(
context,
NotificationUtils.NotificationChannels
.NOTIFICATION_CHANNEL_UPLOADS.name
)
.setContentTitle(failureTitle)
.setContentText(failureText)
.setSmallIcon(R.drawable.baseline_error_24)
.setGroup(NotificationUtils.KEY_UPLOAD_GROUP)
.setOngoing(false)
.build()
// Cancel original notification
mNotifyManager?.cancel(notificationId)
// Then show information about failure
mNotifyManager!!.notify(SystemClock.uptimeMillis().toInt(), notification)
// update current notification with failure info
mNotifyManager!!.notify(SystemClock.uptimeMillis().toInt(), failureNotification)
}
private fun getResourceString(context: Context, resourceId: Int): String {

View File

@ -68,6 +68,10 @@ object NotificationUtils {
// RemoteInput key - used for replies sent directly from notification
const val KEY_DIRECT_REPLY = "key_direct_reply"
// notification group keys
const val KEY_UPLOAD_GROUP = "com.nextcloud.talk.utils.KEY_UPLOAD_GROUP"
const val GROUP_SUMMARY_NOTIFICATION_ID = -1
@TargetApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(
context: Context,

View File

@ -109,6 +109,7 @@
<string name="nc_conversations_empty">会話に参加するか、新しいのを開始</string>
<string name="nc_conversations_empty_details">友達や同僚に挨拶しましょう!</string>
<string name="nc_copy_message">コピー</string>
<string name="nc_create_poll">投票を作成</string>
<string name="nc_date_header_today">今日</string>
<string name="nc_date_header_yesterday">昨日</string>
<string name="nc_delete">削除</string>
@ -144,6 +145,7 @@
<string name="nc_expire_message_one_day">1日</string>
<string name="nc_expire_message_one_hour">1時間</string>
<string name="nc_expire_message_one_week">1週間</string>
<string name="nc_expire_messages_explanation">チャットメッセージは、一定時間経過後に期限切れにすることができます。注意: チャットで共有されたファイルは、ファイル所有者は削除されませんが、会話では共有されなくなります。</string>
<string name="nc_external_server_failed">シグナリング設定を取得できませんでした</string>
<string name="nc_failed_signaling_settings">ターゲットのサーバーは、モバイル経由での公開の会話への参加をサポートしていません。Webブラウザー経由で会話への参加を試みてください。</string>
<string name="nc_failed_to_perform_operation">申し訳ありませんが、何かがおかしいです!</string>
@ -330,6 +332,7 @@
<string name="nc_settings_theme_key">テーマ</string>
<string name="nc_settings_theme_light">ライト</string>
<string name="nc_settings_theme_title">テーマ</string>
<string name="nc_settings_typing_status_desc">自分のタイピング状況を共有し、他の人のタイピング状況を表示する</string>
<string name="nc_settings_use_credentials_title">プロキシは資格情報を必要とします</string>
<string name="nc_settings_warning">警告</string>
<string name="nc_settings_wrong_account">現在のアカウントのみ再認可できます</string>
@ -377,8 +380,11 @@
<string name="open_in_files_app">アプリでファイルを開く</string>
<string name="play_pause_voice_message">音声メッセージを再生/一時停止</string>
<string name="polls_add_option">オプションを追加</string>
<string name="polls_end_poll">投票を終了する</string>
<string name="polls_multiple_answers">複数の答え</string>
<string name="polls_options">オプション</string>
<string name="polls_private_poll">プライベート投票</string>
<string name="polls_question">質問</string>
<string name="polls_results_subtitle">結果</string>
<string name="polls_settings">設定</string>
<string name="polls_submit_vote">投票</string>
@ -386,9 +392,13 @@
<string name="reactions_tab_all">すべて</string>
<string name="read_storage_no_permission">ストレージからのファイル共有は権限がなければ不可能です</string>
<string name="record_cancel_start">録画開始をキャンセル</string>
<string name="record_failed_info">レコーディングが失敗しました。あなたの管理者に連絡してください。</string>
<string name="record_start_description">録画を開始</string>
<string name="record_stop_description">録画を停止</string>
<string name="recording_consent_all">全ての通話には録音の同意が必要です。</string>
<string name="recording_consent_for_conversation_description">この会話に参加する前に、録音の同意が必要です。</string>
<string name="recording_consent_for_conversation_title">レコーディングの同意</string>
<string name="recording_consent_title">通話は録音されるかもしれません。</string>
<string name="recording_settings_title">記録中</string>
<string name="save">保存</string>
<string name="scope_federated_description">信頼できるサーバーのみと同期</string>
@ -417,6 +427,7 @@
<string name="shared_items_file">ファイル</string>
<string name="shared_items_media">メディア</string>
<string name="shared_items_other">その他</string>
<string name="shared_items_poll">投票</string>
<string name="shared_items_voice">音声番号</string>
<string name="starred">お気に入り</string>
<string name="startCallForbidden">通話を開始することが許可されていません</string>
@ -436,6 +447,7 @@
<string name="today">今日</string>
<string name="tomorrow">明日</string>
<string name="translate">翻訳</string>
<string name="translation_copy_translated_text">翻訳されたテキストをコピー</string>
<string name="translation_detect_language">言語を検出する</string>
<string name="translation_device_settings">デバイスの設定</string>
<string name="translation_error_message">言語を検出できませんでした</string>

View File

@ -264,7 +264,7 @@
<string name="nc_settings">Impostatziones</string>
<string name="nc_settings_account_updated">Su contu chi tenias giai est istadu agiornadu, imbetzes de nd\'agiùnghere unu nou</string>
<string name="nc_settings_advanced_title">Avantzadu</string>
<string name="nc_settings_appearance">Visibilidade</string>
<string name="nc_settings_appearance">Aspetu</string>
<string name="nc_settings_call_ringtone">Mutidas</string>
<string name="nc_settings_incognito_keyboard_desc">Imparat a sa tastiera a disativare s\'imparu personale (chene garantzias)</string>
<string name="nc_settings_incognito_keyboard_title">Tastiera in incògnita</string>

View File

@ -143,7 +143,10 @@
<trusted-key id="AFCC4C7594D09E2182C60E0F7A01B0F236E5430F" group="com.google.code.gson"/>
<trusted-key id="B02335AA54CCF21E52BBF9ABD9C565AA72BA2FDD" group="io.grpc"/>
<trusted-key id="B087A0EB8416563AFE64CEBA4604091C01C3086A" group="com.mebigfatguy.fb-contrib" name="fb-contrib" version="7.6.4"/>
<trusted-key id="B41089A2DA79B0FA5810252872385FF0AF338D52" group="joda-time" name="joda-time" version="2.12.6"/>
<trusted-key id="B41089A2DA79B0FA5810252872385FF0AF338D52">
<trusting group="joda-time" name="joda-time" version="2.12.6"/>
<trusting group="joda-time" name="joda-time" version="2.12.7"/>
</trusted-key>
<trusted-key id="B6E73D84EA4FCC47166087253FAAD2CD5ECBB314" group="org.apache.commons" name="commons-parent" version="52"/>
<trusted-key id="B801E2F8EF035068EC1139CC29579F18FA8FD93B" group="com.google.j2objc" name="j2objc-annotations" version="1.3"/>
<trusted-key id="BC87A3FD0A54480F0BADBEBD21939FF0CA2A6567" group="commons-codec" name="commons-codec" version="1.15"/>
@ -1879,6 +1882,14 @@
<sha256 value="89c21ebe5a3aedd8c910bbe0f7c0c6ea6f30dc9dba58d68b39bee3759a7dc52f" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="core" version="0.15.0">
<artifact name="core-0.15.0.aar">
<sha256 value="d370010eeae5928f525f31c09e33f9c78ed5e610af4b9f84b1ba68ce0727267b" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="core-0.15.0.module">
<sha256 value="609090237e7d6c9745dcb883819e53bb4c9337b89740aa88fd1c56d73ecfa56b" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="material-color-utilities" version="0.14.0">
<artifact name="material-color-utilities-0.14.0.jar">
<sha256 value="bfcd5205b056b948bc6ff9a556898188f4a4e92179c723906e10f1164b776eed" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -1887,6 +1898,14 @@
<sha256 value="b12eadfbfe39b7fb6e62a13c9aca93b468e66a0cf0a2d841b849fe86ebc2d605" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="material-color-utilities" version="0.15.0">
<artifact name="material-color-utilities-0.15.0.jar">
<sha256 value="ced5cd660ebfa6aa7461a2157f67a8e76ae12830f759adec0b51631de4ac5434" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="material-color-utilities-0.15.0.module">
<sha256 value="edc221870d47808e96595f11d58dc6f45e50a9d8979658f024ae9a816778ecfb" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="ui" version="0.14.0">
<artifact name="ui-0.14.0.aar">
<sha256 value="2ff1b8ca1ce4d4151ce967c4704a4def19a128d3f710491872e007217f3ac84f" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -1895,6 +1914,14 @@
<sha256 value="a600ef42a3ffe9a5900893f7f68052272ed8749ae2c5c44471f792f9d1d9d01f" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="ui" version="0.15.0">
<artifact name="ui-0.15.0.aar">
<sha256 value="7e520034d730a11f5089548a8f15fbdd5d5054f8f0465f711664235b3939b78d" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="ui-0.15.0.module">
<sha256 value="ac04b3a93aae2e9e8585d01447210fe0c5c85dec13e241b5e7c8956a3ed8a962" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.wooplr" name="Spotlight" version="1.3">
<artifact name="Spotlight-1.3.aar">
<sha256 value="2216a78710c8626623d3fd8f6519ec49e26d86930757b51288b7889653b7b44e" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -2225,6 +2252,14 @@
<sha256 value="f376e72c66dac4215b25d8dcda887ca355c661f630d2d155160e0c6dca7d4ace" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.google.firebase" name="firebase-messaging" version="23.4.1">
<artifact name="firebase-messaging-23.4.1.aar">
<sha256 value="15c2637c28fb6895b6a4c998a191b663f3b78ac815f6ac59ab7d673a8d9af202" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="firebase-messaging-23.4.1.pom">
<sha256 value="188ee169e2d73488e9b14c7610bf3fd9a834658c0759af51b16b0b255865f7c6" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.google.guava" name="failureaccess" version="1.0.1">
<artifact name="failureaccess-1.0.1.pom">
<sha256 value="e96042ce78fecba0da2be964522947c87b40a291b5fd3cd672a434924103c4b9" origin="Generated by Gradle"/>