Merge remote-tracking branch 'upstream/dev' into rtc_calls

# Conflicts:
#	app/build.gradle
This commit is contained in:
jubb
2021-12-20 09:38:13 +11:00
22 changed files with 174 additions and 763 deletions

View File

@@ -155,8 +155,8 @@ dependencies {
testImplementation 'org.robolectric:shadows-multidex:4.4'
}
def canonicalVersionCode = 241
def canonicalVersionName = "1.12.0-ALPHA8"
def canonicalVersionCode = 242
def canonicalVersionName = "1.11.14"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,

View File

@@ -500,6 +500,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
updateSubtitle()
showOrHideInputIfNeeded()
profilePictureView.update(recipient, threadID)
}
}
@@ -713,7 +714,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
private fun handleRecyclerViewScrolled() {
val alpha = if (!isScrolledToBottom) 1.0f else 0.0f
// FIXME: Checking isScrolledToBottom is a quick fix for an issue where the
// typing indicator overlays the recycler view when scrolled up
val wasTypingIndicatorVisibleBefore = typingIndicatorViewContainer.isVisible
@@ -722,7 +722,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
if (isTypingIndicatorVisibleAfter != wasTypingIndicatorVisibleBefore) {
inputBarHeightChanged(inputBar.height)
}
scrollToBottomButton.alpha = alpha
scrollToBottomButton.isVisible = !isScrolledToBottom
unreadCount = min(unreadCount, layoutManager.findFirstVisibleItemPosition())
updateUnreadCountIndicator()
}

View File

@@ -67,7 +67,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
menu.findItem(R.id.menu_context_copy).isVisible = !containsControlMessage && hasText
// Copy Session ID
menu.findItem(R.id.menu_context_copy_public_key).isVisible =
(thread.isGroupRecipient && selectedItems.size == 1 && firstMessage.recipient.address.toString() != userPublicKey)
(thread.isGroupRecipient && !thread.isOpenGroupRecipient && selectedItems.size == 1 && firstMessage.recipient.address.toString() != userPublicKey)
// Message detail
menu.findItem(R.id.menu_message_details).isVisible = (selectedItems.size == 1 && firstMessage.isFailed)
// Resend

View File

@@ -79,7 +79,6 @@ object ConversationMenuHelper {
} else {
inflater.inflate(R.menu.menu_conversation_block, menu)
}
inflater.inflate(R.menu.menu_conversation_copy_session_id, menu)
}
// Closed group menu (options that should only be present in closed groups)
if (thread.isClosedGroupRecipient) {

View File

@@ -16,6 +16,7 @@ import android.widget.RelativeLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.android.synthetic.main.view_visible_message.view.*
@@ -23,6 +24,7 @@ import network.loki.messenger.R
import org.session.libsession.messaging.contacts.Contact.ContactContext
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.utilities.ViewUtil
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.ThreadUtils
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.*
@@ -103,7 +105,9 @@ class VisibleMessageView : LinearLayout {
profilePictureView.publicKey = senderSessionID
profilePictureView.glide = glide
profilePictureView.update(message.individualRecipient, threadID)
profilePictureView.setOnClickListener { showUserDetails(message.recipient.address.toString()) }
profilePictureView.setOnClickListener {
showUserDetails(senderSessionID, threadID)
}
if (thread.isOpenGroupRecipient) {
val openGroup = lokiThreadDb.getOpenGroupChat(threadID) ?: return
val isModerator = OpenGroupAPIV2.isUserModerator(senderSessionID, openGroup.room, openGroup.server)
@@ -379,10 +383,12 @@ class VisibleMessageView : LinearLayout {
pressCallback = null
}
private fun showUserDetails(publicKey: String) {
private fun showUserDetails(publicKey: String, threadID: Long) {
val userDetailsBottomSheet = UserDetailsBottomSheet()
val bundle = Bundle()
bundle.putString("publicKey", publicKey)
val bundle = bundleOf(
UserDetailsBottomSheet.ARGUMENT_PUBLIC_KEY to publicKey,
UserDetailsBottomSheet.ARGUMENT_THREAD_ID to threadID
)
userDetailsBottomSheet.arguments = bundle
val activity = context as AppCompatActivity
userDetailsBottomSheet.show(activity.supportFragmentManager, userDetailsBottomSheet.tag)

View File

@@ -53,6 +53,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
private static final String TIMESTAMP = "timestamp";
private static final String ACTIVE = "active";
private static final String MMS = "mms";
private static final String UPDATED = "updated";
// Loki
private static final String AVATAR_URL = "avatar_url";
@@ -83,11 +84,16 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
private static final String[] GROUP_PROJECTION = {
GROUP_ID, TITLE, MEMBERS, ZOMBIE_MEMBERS, AVATAR, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST,
TIMESTAMP, ACTIVE, MMS, AVATAR_URL, ADMINS
TIMESTAMP, ACTIVE, MMS, AVATAR_URL, ADMINS, UPDATED
};
static final List<String> TYPED_GROUP_PROJECTION = Stream.of(GROUP_PROJECTION).map(columnName -> TABLE_NAME + "." + columnName).toList();
public static String getCreateUpdatedTimestampCommand() {
return "ALTER TABLE "+ TABLE_NAME + " " +
"ADD COLUMN " + UPDATED + " INTEGER DEFAULT 0;";
}
public GroupDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}
@@ -330,6 +336,13 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId});
}
public void updateTimestampUpdated(String groupId, Long updatedTimestamp) {
ContentValues contents = new ContentValues();
contents.put(UPDATED, updatedTimestamp);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId});
}
public void removeMember(String groupId, Address source) {
List<Address> currentMembers = getCurrentMembers(groupId, false);
currentMembers.remove(source);
@@ -439,7 +452,8 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
cursor.getInt(cursor.getColumnIndexOrThrow(MMS)) == 1,
cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_URL)),
cursor.getString(cursor.getColumnIndexOrThrow(ADMINS)),
cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)));
cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)),
cursor.getLong(cursor.getColumnIndexOrThrow(UPDATED)));
}
@Override

View File

@@ -3,13 +3,17 @@ package org.thoughtcrime.securesms.database
import android.content.ContentValues
import android.content.Context
import net.sqlcipher.Cursor
import org.session.libsession.messaging.jobs.*
import org.session.libsession.messaging.jobs.AttachmentUploadJob
import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob
import org.session.libsession.messaging.jobs.Job
import org.session.libsession.messaging.jobs.MessageReceiveJob
import org.session.libsession.messaging.jobs.MessageSendJob
import org.session.libsession.messaging.jobs.SessionJobInstantiator
import org.session.libsession.messaging.jobs.SessionJobManagerFactories
import org.session.libsession.messaging.utilities.Data
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.database.*
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer
import org.thoughtcrime.securesms.util.*
class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
@@ -78,6 +82,13 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
}
}
fun getGroupAvatarDownloadJob(server: String, room: String): GroupAvatarDownloadJob? {
val database = databaseHelper.readableDatabase
return database.getAll(sessionJobTable, "$jobType = ?", arrayOf(GroupAvatarDownloadJob.KEY)) {
jobFromCursor(it) as GroupAvatarDownloadJob?
}.filterNotNull().find { it.server == server && it.room == room }
}
fun cancelPendingMessageSendJobs(threadID: Long) {
val database = databaseHelper.writableDatabase
val attachmentUploadJobKeys = mutableListOf<String>()

View File

@@ -186,6 +186,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return DatabaseComponent.get(context).sessionJobDatabase().getMessageReceiveJob(messageReceiveJobID)
}
override fun getGroupAvatarDownloadJob(server: String, room: String): GroupAvatarDownloadJob? {
return DatabaseComponent.get(context).sessionJobDatabase().getGroupAvatarDownloadJob(server, room)
}
override fun resumeMessageSendJobIfNeeded(messageSendJobID: String) {
val job = DatabaseComponent.get(context).sessionJobDatabase().getMessageSendJob(messageSendJobID) ?: return
JobQueue.shared.resumePendingSendMessage(job)
@@ -469,6 +473,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
.updateFormationTimestamp(groupID, formationTimestamp)
}
override fun updateTimestampUpdated(groupID: String, updatedTimestamp: Long) {
DatabaseComponent.get(context).groupDatabase()
.updateTimestampUpdated(groupID, updatedTimestamp)
}
override fun setExpirationTimer(groupID: String, duration: Int) {
val recipient = Recipient.from(context, fromSerialized(groupID), false)
DatabaseComponent.get(context).recipientDatabase().setExpireMessages(recipient, duration);

View File

@@ -61,9 +61,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV27 = 48;
private static final int lokiV28 = 49;
private static final int lokiV29 = 50;
private static final int lokiV30 = 51;
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
private static final int DATABASE_VERSION = lokiV29;
private static final int DATABASE_VERSION = lokiV30;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@@ -136,6 +137,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(SessionContactDatabase.getCreateSessionContactTableCommand());
db.execSQL(RecipientDatabase.getCreateNotificationTypeCommand());
db.execSQL(ThreadDatabase.getCreatePinnedCommand());
db.execSQL(GroupDatabase.getCreateUpdatedTimestampCommand());
executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS);
@@ -314,6 +316,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(ThreadDatabase.getCreatePinnedCommand());
}
if (oldVersion < lokiV30) {
db.execSQL(GroupDatabase.getCreateUpdatedTimestampCommand());
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();

View File

@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.groups
import android.content.Context
import android.graphics.Bitmap
import androidx.annotation.WorkerThread
import okhttp3.HttpUrl
import org.session.libsession.messaging.MessagingModuleConfiguration
@@ -72,18 +71,9 @@ object OpenGroupManager {
OpenGroupAPIV2.getAuthToken(room, server).get()
// Get group info
val info = OpenGroupAPIV2.getInfo(room, server).get()
// Download the group image
// FIXME: Don't wait for the image to download
val image: Bitmap?
// Create the group locally if not available already
if (threadID < 0) {
val profilePictureAsByteArray = try {
OpenGroupAPIV2.downloadOpenGroupProfilePicture(info.id, server).get()
} catch (e: Exception) {
null
}
image = BitmapUtil.fromByteArray(profilePictureAsByteArray)
// Create the group locally
threadID = GroupManager.createOpenGroup(openGroupID, context, image, info.name).threadId
threadID = GroupManager.createOpenGroup(openGroupID, context, null, info.name).threadId
}
val openGroup = OpenGroupV2(server, room, info.name, publicKey)
threadDB.setOpenGroupChat(openGroup, threadID)

View File

@@ -258,8 +258,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis
bottomSheet.onViewDetailsTapped = {
bottomSheet.dismiss()
val userDetailsBottomSheet = UserDetailsBottomSheet()
val bundle = Bundle()
bundle.putString("publicKey", thread.recipient.address.toString())
val bundle = bundleOf(
UserDetailsBottomSheet.ARGUMENT_PUBLIC_KEY to thread.recipient.address.toString(),
UserDetailsBottomSheet.ARGUMENT_THREAD_ID to thread.threadId
)
userDetailsBottomSheet.arguments = bundle
userDetailsBottomSheet.show(supportFragmentManager, userDetailsBottomSheet.tag)
}

View File

@@ -12,7 +12,10 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.core.view.isVisible
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import dagger.hilt.EntryPoint
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.android.synthetic.main.fragment_user_details_bottom_sheet.*
import network.loki.messenger.R
import org.session.libsession.messaging.MessagingModuleConfiguration
@@ -20,20 +23,32 @@ import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.util.UiModeUtilities
import javax.inject.Inject
@AndroidEntryPoint
class UserDetailsBottomSheet : BottomSheetDialogFragment() {
@Inject lateinit var threadDb: ThreadDatabase
companion object {
const val ARGUMENT_PUBLIC_KEY = "publicKey"
const val ARGUMENT_THREAD_ID = "threadId"
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_user_details_bottom_sheet, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val publicKey = arguments?.getString("publicKey") ?: return dismiss()
val publicKey = arguments?.getString(ARGUMENT_PUBLIC_KEY) ?: return dismiss()
val threadID = arguments?.getLong(ARGUMENT_THREAD_ID) ?: return dismiss()
val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false)
val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss()
profilePictureView.publicKey = publicKey
profilePictureView.glide = GlideApp.with(this)
profilePictureView.isLarge = true
@@ -65,6 +80,9 @@ class UserDetailsBottomSheet : BottomSheetDialogFragment() {
}
}
nameTextView.text = recipient.name ?: publicKey // Uses the Contact API internally
publicKeyTextView.isVisible = !threadRecipient.isOpenGroupRecipient
messageButton.isVisible = !threadRecipient.isOpenGroupRecipient
publicKeyTextView.text = publicKey
publicKeyTextView.setOnLongClickListener {
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager

View File

@@ -23,8 +23,6 @@ class LandingActivity : BaseActionBarActivity() {
findViewById<View>(R.id.linkButton).setOnClickListener { link() }
IdentityKeyUtil.generateIdentityKeyPair(this)
TextSecurePreferences.setPasswordDisabled(this, true)
TextSecurePreferences.setReadReceiptsEnabled(this, true)
TextSecurePreferences.setTypingIndicatorsEnabled(this, true)
// AC: This is a temporary workaround to trick the old code that the screen is unlocked.
KeyCachingService.setMasterSecret(applicationContext, Object())
}

View File

@@ -94,8 +94,7 @@
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginRight="12dp"
android:layout_marginBottom="72dp"
android:alpha="1">
android:layout_marginBottom="72dp">
<RelativeLayout
android:layout_width="40dp"

View File

@@ -1,718 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">mECC</string>
<string name="yes">Da</string>
<string name="no">Ne</string>
<string name="delete">Obriši</string>
<string name="ban">Banuj</string>
<string name="please_wait">Sačekaj...</string>
<string name="save">Spremi</string>
<string name="note_to_self">Osobna bilješka</string>
<string name="version_s">Verzija %s</string>
<!-- AbstractNotificationBuilder -->
<string name="AbstractNotificationBuilder_new_message">Nova poruka</string>
<!-- AlbumThumbnailView -->
<string name="AlbumThumbnailView_plus">\+%d</string>
<!-- ApplicationPreferencesActivity -->
<plurals name="ApplicationPreferencesActivity_messages_per_conversation">
<item quantity="one">%d poruka po razgovoru</item>
<item quantity="few">%d poruke po razgovoru</item>
<item quantity="many">%d poruka po razgovoru</item>
<item quantity="other">%d poruka po razgovoru</item>
</plurals>
<string name="ApplicationPreferencesActivity_delete_all_old_messages_now">Obriši sve stare poruke?</string>
<plurals name="ApplicationPreferencesActivity_this_will_immediately_trim_all_conversations_to_the_d_most_recent_messages">
<item quantity="one">Ovo će automatski skratiti sve razgovore na odabrani broj poruka.</item>
<item quantity="few">Ovo će automatski skratiti sve razgovore na %d najnovije poruke.</item>
<item quantity="many">Ovo će automatski skratiti sve razgovore na %d najnovijih poruka.</item>
<item quantity="other">Ovo će automatski skratiti sve razgovore na %d najnovijih poruka.</item>
</plurals>
<string name="ApplicationPreferencesActivity_delete">Obriši</string>
<string name="ApplicationPreferencesActivity_On">Uključeno</string>
<string name="ApplicationPreferencesActivity_Off">Isključeno</string>
<!-- DraftDatabase -->
<string name="DraftDatabase_Draft_image_snippet">(slika)</string>
<string name="DraftDatabase_Draft_audio_snippet">(zvuk)</string>
<string name="DraftDatabase_Draft_video_snippet">(video)</string>
<string name="DraftDatabase_Draft_quote_snippet">(odgovor)</string>
<!-- AttachmentManager -->
<string name="AttachmentManager_cant_open_media_selection">Nije moguće pronaći aplikaciju za odabir medija.</string>
<string name="AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio">mECC zahtijeva dozvolu pristupa Pohrani podataka za umetanje slikovnih i audio-vizualnih priloga ali zahtjev biva odbijen. Molim otvorite opcije aplikacije, odaberite \"Dozvole\" i uključite \"Pohrana\".</string>
<string name="AttachmentManager_signal_requires_contacts_permission_in_order_to_attach_contact_information">mECC zahtijeva dozvolu pristupa Kontaktima za prilaganje informacije o kontaktima ali pristup biva odbijen. Molim otvorite opcije aplikacije, odaberite \"Dozvole\" i uključite \"Kontakti\".</string>
<string name="AttachmentManager_signal_requires_the_camera_permission_in_order_to_take_photos_but_it_has_been_permanently_denied">mECC zahtijeva dozvolu pristupa Kameri za omogućavanje slikanja ali pristup biva odbijen. Molim otvorite opcije aplikacije, odaberite \"Dozvole\" i uključite \"Kamera\".</string>
<!-- AudioSlidePlayer -->
<string name="AudioSlidePlayer_error_playing_audio">Greška prilikom reprodukcije zvuka!</string>
<!-- BucketedThreadMedia -->
<string name="BucketedThreadMedia_Today">Danas</string>
<string name="BucketedThreadMedia_Yesterday">Juče</string>
<string name="BucketedThreadMedia_This_week">Ova sedmica</string>
<string name="BucketedThreadMedia_This_month">Ovaj mjesec</string>
<!-- CommunicationActions -->
<string name="CommunicationActions_no_browser_found">Web pretraživač nije pronađen.</string>
<!-- ContactsCursorLoader -->
<string name="ContactsCursorLoader_groups">Grupe</string>
<!-- ConversationItem -->
<string name="ConversationItem_error_not_delivered">Slanje nije uspjelo, dodirni za detalje</string>
<string name="ConversationItem_received_key_exchange_message_tap_to_process">Poruka za razmjenu ključeva je primljena, pritisni da nastaviš.</string>
<string name="ConversationItem_group_action_left">%1$s je napustio grupu.</string>
<string name="ConversationItem_click_to_approve_unencrypted">Slanje neuspješno, pritisni za nesigurnu rezervu</string>
<string name="ConversationItem_unable_to_open_media">Nije moguće pronaći aplikaciju za otvaranje ovog medija.</string>
<string name="ConversationItem_copied_text">Kopirano %s</string>
<string name="ConversationItem_download_more">Preuzmi više</string>
<string name="ConversationItem_pending">Na čekanju</string>
<!-- ConversationActivity -->
<string name="ConversationActivity_add_attachment">Dodaj prilog</string>
<string name="ConversationActivity_select_contact_info">Odaberite informacije kontakta</string>
<string name="ConversationActivity_sorry_there_was_an_error_setting_your_attachment">Došlo je do greške prilikom postavljanja priloga.</string>
<string name="ConversationActivity_message">Poruka</string>
<string name="ConversationActivity_compose">Sastavi</string>
<string name="ConversationActivity_muted_until_date">Prigušen do %1$s</string>
<string name="ConversationActivity_member_count">%1$d Dodaj članove</string>
<string name="ConversationActivity_open_group_guidelines">Pravila zajednice</string>
<string name="ConversationActivity_invalid_recipient">Neispravan primatelj!</string>
<string name="ConversationActivity_added_to_home_screen">Dodano na početni ekran</string>
<string name="ConversationActivity_leave_group">Napusti grupu?</string>
<string name="ConversationActivity_are_you_sure_you_want_to_leave_this_group">Jesi li siguran da želiš napustiti ovu grupu?</string>
<string name="ConversationActivity_error_leaving_group">Greška pri napuštanju grupe</string>
<string name="ConversationActivity_unblock_this_contact_question">Ukloni blokadu ovog kontakta?</string>
<string name="ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact">Ponovno ćeš moći primati poruke ili pozive ovog korisnika.</string>
<string name="ConversationActivity_unblock">Ukloni blokadu</string>
<string name="ConversationActivity_attachment_exceeds_size_limits">Prilog prelazi ograničenje veličine za tip poruke koju šalješ.</string>
<string name="ConversationActivity_unable_to_record_audio">Nije moguće snimati svuk!</string>
<string name="ConversationActivity_there_is_no_app_available_to_handle_this_link_on_your_device">Na tvom uređaju ne postoji aplikacija koja bi rukovala ovim linkom.</string>
<string name="ConversationActivity_invite_to_open_group">Dodaj članove</string>
<string name="ConversationActivity_join_open_group">Pridruži se %s</string>
<string name="ConversationActivity_join_open_group_confirmation_message">Jeste li sigurni da se želite pridružiti otvorenoj grupi <b>%s</b>?</string>
<string name="ConversationActivity_to_send_audio_messages_allow_signal_access_to_your_microphone">Kako biste poslali audio poruku, dozvolite mECC-u pristup vašem mikrofonu.</string>
<string name="ConversationActivity_signal_requires_the_microphone_permission_in_order_to_send_audio_messages">mECC zahtijeva dozvolu pristupa Mikrofonu za slanje zvučnih poruka ali pristup biva odbijen. Molim otvorite opcije aplikacije, odaberite \"Dozvole\" i uključite \"Mikrofon\".</string>
<string name="ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera">Kako biste snimili slike i video, dozvolite mECC-u pristup kameri.</string>
<string name="ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video">mECC zahtijeva dozvolu Kameri za fotografiranje ili snimanje videa, ali pristup biva odbijen. Molim nastavite s postavkama aplikacije, odaberite \"Dozvole\" i omogućite \"Kamera\".</string>
<string name="ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video">mECC treba pristup kameri kako bi snimio slike ili video.</string>
<string name="ConversationActivity_quoted_contact_message">%1$s%2$s</string>
<string name="ConversationActivity_search_position">%1$d od %2$d</string>
<string name="ConversationActivity_no_results">Nema rezultata</string>
<!-- ConversationAdapter -->
<plurals name="ConversationAdapter_n_unread_messages">
<item quantity="one">%d nepročitana poruka</item>
<item quantity="few">%d nepročitane poruke</item>
<item quantity="many">%d nepročitanih poruka</item>
<item quantity="other">%d nepročitanih poruka</item>
</plurals>
<!-- ConversationFragment -->
<plurals name="ConversationFragment_delete_selected_messages">
<item quantity="one">Obriši odabrane poruke?</item>
<item quantity="few">Obriši odabrane poruke?</item>
<item quantity="many">Obriši odabrane poruke?</item>
<item quantity="other">Obriši odabrane poruke?</item>
</plurals>
<plurals name="ConversationFragment_this_will_permanently_delete_all_n_selected_messages">
<item quantity="one">Ovo će trajno obrisati odabrane poruke.</item>
<item quantity="few">Ovo će trajno obrisati sve %1$d odabrane poruke.</item>
<item quantity="many">Ovo će trajno obrisati svih %1$d odabranih poruka.</item>
<item quantity="other">Ovo će trajno obrisati svih %1$d odabranih poruka.</item>
</plurals>
<string name="ConversationFragment_ban_selected_user">Banovati ovog korisnika?</string>
<string name="ConversationFragment_save_to_sd_card">Spremi na disk?</string>
<plurals name="ConversationFragment_saving_n_media_to_storage_warning">
<item quantity="one">Spremanje svih medija na disk će omogućiti pristup mediju iz drugih aplikacija na vašem uređaju.\n\nNastavi?</item>
<item quantity="few">Spremanje sva %1$d medija na disk će omogućiti pristup mediju iz drugih aplikacija na vašem uređaju.\n\nNastavi?</item>
<item quantity="many">Spremanje svih %1$d medija na disk će omogućiti pristup mediju iz drugih aplikacija na vašem uređaju.\n\nNastavi?</item>
<item quantity="other">Spremanje svih %1$d medija na disk će omogućiti pristup mediju iz drugih aplikacija na vašem uređaju.\n\nNastavi?</item>
</plurals>
<plurals name="ConversationFragment_error_while_saving_attachments_to_sd_card">
<item quantity="one">Greška prilikom spremanja privitaka na disk!</item>
<item quantity="few">Greška prilikom spremanja privitaka na disk!</item>
<item quantity="many">Greška prilikom spremanja privitaka na disk!</item>
<item quantity="other">Greška prilikom spremanja priloga na disk!</item>
</plurals>
<plurals name="ConversationFragment_saving_n_attachments">
<item quantity="one">Spremanje priloga</item>
<item quantity="few">Spremanje %1$d priloga</item>
<item quantity="many">Spremanje %1$d priloga</item>
<item quantity="other">Spremanje %1$d priloga</item>
</plurals>
<plurals name="ConversationFragment_saving_n_attachments_to_sd_card">
<item quantity="one">Spremanje prologa na disk...</item>
<item quantity="few">Spremanje %1$d priloga na disk...</item>
<item quantity="many">Spremanje %1$d priloga na disk...</item>
<item quantity="other">Spremanje %1$d priloga na disk...</item>
</plurals>
<string name="ConversationFragment_pending">U toku...</string>
<string name="ConversationFragment_push">Podaci (mECC)</string>
<string name="ConversationFragment_mms">MMS</string>
<string name="ConversationFragment_sms">SMS</string>
<string name="ConversationFragment_deleting">Brisanje</string>
<string name="ConversationFragment_deleting_messages">Brisanje poruka...</string>
<string name="ConversationFragment_banning">Banovanje</string>
<string name="ConversationFragment_banning_user">Banovanje korisnika…</string>
<string name="ConversationFragment_quoted_message_not_found">Originalna poruka nije pronađena</string>
<string name="ConversationFragment_quoted_message_no_longer_available">Originalna poruka više nije dostupna</string>
<!-- ConversationListItem -->
<string name="ConversationListItem_key_exchange_message">Poruka za razmjenu ključa</string>
<!-- CreateProfileActivity -->
<string name="CreateProfileActivity_profile_photo">Profilna slika</string>
<!-- CustomDefaultPreference -->
<string name="CustomDefaultPreference_using_custom">Koristeći prilagođeno: %s</string>
<string name="CustomDefaultPreference_using_default">Koristeći zadano: %s</string>
<string name="CustomDefaultPreference_none">Niti jedna</string>
<!-- DateUtils -->
<string name="DateUtils_just_now">Sada</string>
<string name="DateUtils_minutes_ago">%d min</string>
<string name="DateUtils_today">Danas</string>
<string name="DateUtils_yesterday">Juče</string>
<!-- DeviceListItem -->
<string name="DeviceListItem_today">Danas</string>
<!-- DocumentView -->
<string name="DocumentView_unknown_file">Nepoznata datoteka</string>
<!-- GiphyActivity -->
<string name="GiphyActivity_error_while_retrieving_full_resolution_gif">Greška pri dohvaćanju GIFa pune rezolucije</string>
<!-- GiphyFragmentPageAdapter -->
<string name="GiphyFragmentPagerAdapter_gifs">GIFovi</string>
<string name="GiphyFragmentPagerAdapter_stickers">Naljepnice</string>
<!-- CropImageActivity -->
<string name="CropImageActivity_profile_avatar">Slika profila</string>
<!-- InputPanel -->
<string name="InputPanel_tap_and_hold_to_record_a_voice_message_release_to_send">Pritisni i drži kako bi snimio glasovnu poruku, pusti za slanje</string>
<!-- LongMessageActivity -->
<string name="LongMessageActivity_unable_to_find_message">Nije moguće naći poruku</string>
<string name="LongMessageActivity_message_from_s">Poruka od %1$s</string>
<string name="LongMessageActivity_your_message">Tvoja poruka</string>
<!-- MediaOverviewActivity -->
<string name="MediaOverviewActivity_Media">Medij</string>
<plurals name="MediaOverviewActivity_Media_delete_confirm_title">
<item quantity="one">Izbriši označenu poruku?</item>
<item quantity="few">Izbriši označene poruke?</item>
<item quantity="many">Izbriši označene poruke?</item>
<item quantity="other">Izbriši označene poruke?</item>
</plurals>
<plurals name="MediaOverviewActivity_Media_delete_confirm_message">
<item quantity="one">Ovo će trajno obrisati označenu poruku.</item>
<item quantity="few">Broj poruka koji će biti trajno izbrisan: %1$d</item>
<item quantity="many">Broj poruka koji će biti trajno izbrisan: %1$d</item>
<item quantity="other">Broj poruka koji će biti trajno izbrisan: %1$d</item>
</plurals>
<string name="MediaOverviewActivity_Media_delete_progress_title">Brisanje</string>
<string name="MediaOverviewActivity_Media_delete_progress_message">Brisanje poruka...</string>
<string name="MediaOverviewActivity_Documents">Dokumenti</string>
<string name="MediaOverviewActivity_Select_all">Odaberi sve</string>
<string name="MediaOverviewActivity_collecting_attachments">Prikupljanje priloga...</string>
<!-- NotificationMmsMessageRecord -->
<string name="NotificationMmsMessageRecord_multimedia_message">Multimedijalna poruka</string>
<string name="NotificationMmsMessageRecord_downloading_mms_message">Preuzimanje MMS poruke</string>
<string name="NotificationMmsMessageRecord_error_downloading_mms_message">Greška pri preuzimanju MMS poruke, pritisni za ponovni pokušaj</string>
<!-- MediaPickerActivity -->
<string name="MediaPickerActivity_send_to">Pošalji u %s</string>
<!-- MediaSendActivity -->
<string name="MediaSendActivity_add_a_caption">Dodaj naslov...</string>
<string name="MediaSendActivity_an_item_was_removed_because_it_exceeded_the_size_limit">Stavka je uklonjena jer je prešla limit veličine</string>
<string name="MediaSendActivity_camera_unavailable">Kamera nije dostupna</string>
<string name="MediaSendActivity_message_to_s">Poruka prema %s</string>
<plurals name="MediaSendActivity_cant_share_more_than_n_items">
<item quantity="one">Ne možeš dijeliti više od %dstavke.</item>
<item quantity="few">Ne možeš dijeliti više od %d stavki.</item>
<item quantity="many">Ne možeš dijeliti više od %dstavki.</item>
<item quantity="other">Ne možeš dijeliti više od %dstavki.</item>
</plurals>
<!-- MediaRepository -->
<string name="MediaRepository_all_media">Svi mediji</string>
<!-- MessageRecord -->
<string name="MessageRecord_message_encrypted_with_a_legacy_protocol_version_that_is_no_longer_supported">Primljena je poruka kriptirana starom verzijom mECC aplikacije koja više nije podržana. Natjeraj Ba pošiljatelja da ažurira na najnoviju verziju aplikacije i ponovno pošalje poruku.</string>
<string name="MessageRecord_left_group">Napustio si grupu.</string>
<string name="MessageRecord_you_updated_group">Ažurao si grupu.</string>
<string name="MessageRecord_s_updated_group">%s je ažurirao grupu.</string>
<!-- ExpirationDialog -->
<string name="ExpirationDialog_disappearing_messages">Nestajuće poruke</string>
<string name="ExpirationDialog_your_messages_will_not_expire">Tvoje poruke neće isteći.</string>
<string name="ExpirationDialog_your_messages_will_disappear_s_after_they_have_been_seen">Primljene i poslane poruke u ovom razgovoru će nestati %s nakon što su viđene.</string>
<!-- PassphrasePromptActivity -->
<string name="PassphrasePromptActivity_enter_passphrase">Unesi lozinku</string>
<!-- RecipientPreferencesActivity -->
<string name="RecipientPreferenceActivity_block_this_contact_question">Blokiraj kontakt?</string>
<string name="RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact">Nećeš više primati poruke i pozive ovog korisnika.</string>
<string name="RecipientPreferenceActivity_block">Blokiraj</string>
<string name="RecipientPreferenceActivity_unblock_this_contact_question">Ukloni blokadu ovog kontakta?</string>
<string name="RecipientPreferenceActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact">Ponovno ćete moći primati poruke ili pozive ovog korisnika.</string>
<string name="RecipientPreferenceActivity_unblock">Ukloni blokadu</string>
<!-- Slide -->
<string name="Slide_image">Slika</string>
<string name="Slide_audio">Audio</string>
<string name="Slide_video">Video</string>
<!-- SmsMessageRecord -->
<string name="SmsMessageRecord_received_corrupted_key_exchange_message">Primljena iskvarena poruka
razmjene ključeva!</string>
<string name="SmsMessageRecord_received_key_exchange_message_for_invalid_protocol_version">Primljena poruka razmjene ključeva za pogrešnu veryiju protokola. </string>
<string name="SmsMessageRecord_received_message_with_new_safety_number_tap_to_process">Primljena je poruka s novim sigurnosnim brojem. Pritisni za obradu i prikaz.</string>
<string name="SmsMessageRecord_secure_session_reset">Resetiranje sigurne sesije mECC-a.</string>
<string name="SmsMessageRecord_secure_session_reset_s">%s resetiranje sigurne sesije mECC-a.</string>
<string name="SmsMessageRecord_duplicate_message">Dupla poruka.</string>
<!-- ThreadRecord -->
<string name="ThreadRecord_group_updated">Grupa je ažurirana</string>
<string name="ThreadRecord_left_the_group">Napustio/la grupu</string>
<string name="ThreadRecord_secure_session_reset">Resetiranje sigurne sesije eMCC-a.</string>
<string name="ThreadRecord_draft">Skica:</string>
<string name="ThreadRecord_called">Zvao si</string>
<string name="ThreadRecord_called_you">Zvao te</string>
<string name="ThreadRecord_missed_call">Propušteni poziv</string>
<string name="ThreadRecord_media_message">Multimedijalna poruka</string>
<string name="ThreadRecord_s_is_on_signal">%s je dostupan na mECC-u!</string>
<string name="ThreadRecord_disappearing_messages_disabled">Poruke koje nestaju onemogućene</string>
<string name="ThreadRecord_disappearing_message_time_updated_to_s">Vrijeme nestajanja poruke postavljeno na %s</string>
<string name="ThreadRecord_s_took_a_screenshot">%s je napravio snimak zaslona.</string>
<string name="ThreadRecord_media_saved_by_s">%s je sačuvao mediju.</string>
<string name="ThreadRecord_safety_number_changed">Sigurnosni broj je izmijenjen</string>
<string name="ThreadRecord_your_safety_number_with_s_has_changed">Vaš sigurnosni broj s %s je izmjenjen.</string>
<string name="ThreadRecord_you_marked_verified">Označio si provjerenim</string>
<string name="ThreadRecord_you_marked_unverified">Označio si nepotvrđenim</string>
<string name="ThreadRecord_empty_message">Ovaj razgovor je prazan</string>
<string name="ThreadRecord_open_group_invitation">Otvori pozivnicu za grupu</string>
<!-- UpdateApkReadyListener -->
<string name="UpdateApkReadyListener_Signal_update">mECC ažuriranje</string>
<string name="UpdateApkReadyListener_a_new_version_of_signal_is_available_tap_to_update">Nova verza mECC-a je dostupna, pritisni za ažuriranje</string>
<!-- MessageDisplayHelper -->
<string name="MessageDisplayHelper_bad_encrypted_message">Loše kriptirana poruka</string>
<string name="MessageDisplayHelper_message_encrypted_for_non_existing_session">Poruka kriptirana za nepostojeću sesiju</string>
<!-- MmsMessageRecord -->
<string name="MmsMessageRecord_bad_encrypted_mms_message">Loše kriptirana MMS poruka</string>
<string name="MmsMessageRecord_mms_message_encrypted_for_non_existing_session">MMS poruka kriptirana za nepostojeću sesiju</string>
<!-- MuteDialog -->
<string name="MuteDialog_mute_notifications">Utišaj obavijesti</string>
<!-- KeyCachingService -->
<string name="KeyCachingService_signal_passphrase_cached">Pritisni za otvaranje.</string>
<string name="KeyCachingService_passphrase_cached">mECC je otključan</string>
<string name="KeyCachingService_lock">Zaključaj mECC</string>
<!-- MediaPreviewActivity -->
<string name="MediaPreviewActivity_you">Ti</string>
<string name="MediaPreviewActivity_unssuported_media_type">Nepodržani tip medija</string>
<string name="MediaPreviewActivity_draft">Skica</string>
<string name="MediaPreviewActivity_unable_to_write_to_external_storage_without_permission">Nije moguće spremanje na vanjski medij bez dozvola</string>
<string name="MediaPreviewActivity_media_delete_confirmation_title">Obriši poruku?</string>
<string name="MediaPreviewActivity_media_delete_confirmation_message">Radnja će trajno izbrisati ovu poruku.</string>
<!-- MessageNotifier -->
<string name="MessageNotifier_d_new_messages_in_d_conversations">%1$d novih poruka u %2$d razgovora</string>
<string name="MessageNotifier_most_recent_from_s">Najnovije od: %1$s</string>
<string name="MessageNotifier_locked_message">Zaključana poruka</string>
<string name="MessageNotifier_message_delivery_failed">Neuspješna isporuka poruke.</string>
<string name="MessageNotifier_failed_to_deliver_message">Isporuka poruke nije uspjela.</string>
<string name="MessageNotifier_error_delivering_message">Greška prilikom isporuke poruke.</string>
<string name="MessageNotifier_mark_all_as_read">Označi sve kao pročitano</string>
<string name="MessageNotifier_mark_read">Označi pročitano</string>
<string name="MessageNotifier_reply">Odgovori</string>
<string name="MessageNotifier_pending_signal_messages">mECC poruke na čekanju</string>
<string name="MessageNotifier_you_have_pending_signal_messages">Imaš mECC poruka na čekanju, pritisni kako bi ih otvorio i preuzeo</string>
<string name="MessageNotifier_contact_message">%1$s%2$s</string>
<string name="MessageNotifier_unknown_contact_message">Kontakt</string>
<!-- Notification Channels -->
<string name="NotificationChannel_messages">Zadano</string>
<string name="NotificationChannel_calls">Pozivi</string>
<string name="NotificationChannel_failures">Neuspjesi</string>
<string name="NotificationChannel_backups">Sigurnosne kopije</string>
<string name="NotificationChannel_locked_status">Status zaključavanja</string>
<string name="NotificationChannel_app_updates">Ažuriranja aplikacije</string>
<string name="NotificationChannel_other">Ostalo</string>
<string name="NotificationChannel_group_messages">Poruke</string>
<string name="NotificationChannel_missing_display_name">Nepoznato</string>
<!-- QuickResponseService -->
<string name="QuickResponseService_quick_response_unavailable_when_Signal_is_locked">Brzi odgovor nije dostupan kada je mECC zaključan!</string>
<string name="QuickResponseService_problem_sending_message">Greška prilikom slanja poruke!</string>
<!-- SaveAttachmentTask -->
<string name="SaveAttachmentTask_saved_to">Spremljeno u %s</string>
<string name="SaveAttachmentTask_saved">Sačuvano</string>
<!-- SearchToolbar -->
<string name="SearchToolbar_search">Traži</string>
<!-- ShortcutLauncherActivity -->
<string name="ShortcutLauncherActivity_invalid_shortcut">Nevažeći prečac</string>
<!-- SingleRecipientNotificationBuilder -->
<string name="SingleRecipientNotificationBuilder_signal">mECC</string>
<string name="SingleRecipientNotificationBuilder_new_message">Nova poruka</string>
<!-- TransferControlView -->
<!-- VideoPlayer -->
<string name="VideoPlayer_error_playing_video">Greška pri video reprodukciji</string>
<!-- attachment_type_selector -->
<string name="attachment_type_selector__audio">Zvuk</string>
<string name="attachment_type_selector__audio_description">Zvuk</string>
<string name="attachment_type_selector__contact">Kontakt</string>
<string name="attachment_type_selector__contact_description">Kontakt</string>
<string name="attachment_type_selector__camera">Kamera</string>
<string name="attachment_type_selector__camera_description">Kamera</string>
<string name="attachment_type_selector__location">Položaj</string>
<string name="attachment_type_selector__location_description">Položaj</string>
<string name="attachment_type_selector__gif">GIF</string>
<string name="attachment_type_selector__gif_description">Gif</string>
<string name="attachment_type_selector__gallery_description">Slika ili video</string>
<string name="attachment_type_selector__file_description">Datoteka</string>
<string name="attachment_type_selector__gallery">Galerija</string>
<string name="attachment_type_selector__file">Datoteka</string>
<string name="attachment_type_selector__drawer_description">Uključi/isključi ladicu dodataka</string>
<!-- contact_selection_group_activity -->
<string name="contact_selection_group_activity__finding_contacts">Učitavanje kontakata...</string>
<!-- conversation_activity -->
<string name="conversation_activity__send">Pošalji</string>
<string name="conversation_activity__compose_description">Sastavljanje poruke</string>
<string name="conversation_activity__emoji_toggle_description">Uključi/isključi emoji tipkovnicu</string>
<string name="conversation_activity__attachment_thumbnail">Priložena sličica</string>
<string name="conversation_activity__quick_attachment_drawer_toggle_camera_description">Uključi/isključi ladicu brze kamere</string>
<string name="conversation_activity__quick_attachment_drawer_record_and_send_audio_description">Snimi i pošalji audio privitak</string>
<string name="conversation_activity__quick_attachment_drawer_lock_record_description">Zaključaj snimanje audio priloga</string>
<string name="conversation_activity__enable_signal_for_sms">Omogući mECC za SMS</string>
<!-- conversation_input_panel -->
<string name="conversation_input_panel__slide_to_cancel">Klizni za otkazivanje</string>
<string name="conversation_input_panel__cancel">Odustani</string>
<!-- conversation_item -->
<string name="conversation_item__mms_image_description">Multimedijalna poruka</string>
<string name="conversation_item__secure_message_description">Sigurna poruka</string>
<!-- conversation_item_sent -->
<string name="conversation_item_sent__send_failed_indicator_description">Neuspješno slanje</string>
<string name="conversation_item_sent__pending_approval_description">Odobrenje u tijeku</string>
<string name="conversation_item_sent__delivered_description">Isporučeno</string>
<string name="conversation_item_sent__message_read">Poruka pročitana</string>
<!-- conversation_item_received -->
<string name="conversation_item_received__contact_photo_description">Slika kontakta</string>
<!-- audio_view -->
<string name="audio_view__play_accessibility_description">Reprodukcija</string>
<string name="audio_view__pause_accessibility_description">Zaustavi</string>
<string name="audio_view__download_accessibility_description">Preuzmi</string>
<!-- open_group_invitation_view -->
<string name="open_group_invitation_view__join_accessibility_description">Pridruži se</string>
<string name="open_group_invitation_view__open_group_invitation">Otvori pozivnicu za grupu</string>
<string name="open_group_guidelines_pinned_message">Prikačena poruka</string>
<string name="open_group_guidelines_community_guidelines">Pravila zajednice</string>
<string name="open_group_guidelines_read">Pročitaj</string>
<!-- QuoteView -->
<string name="QuoteView_audio">Zvuk</string>
<string name="QuoteView_video">Video</string>
<string name="QuoteView_photo">Fotografija</string>
<string name="QuoteView_you">Vi</string>
<string name="QuoteView_original_missing">Originalna poruka nije pronađena</string>
<!-- conversation_fragment -->
<string name="conversation_fragment__scroll_to_the_bottom_content_description">Kliži do dna</string>
<!-- giphy_activity -->
<string name="giphy_activity_toolbar__search_gifs_and_stickers">Pretraži GIFove i naljepnice</string>
<!-- giphy_fragment -->
<string name="giphy_fragment__nothing_found">Ništa nije pronađeno.</string>
<!-- load_more_header -->
<string name="load_more_header__see_full_conversation">Prikaži cijeli razgovor</string>
<string name="load_more_header__loading">Učitavanje</string>
<!-- media_overview_activity -->
<string name="media_overview_activity__no_media">Bez medija</string>
<!-- message_recipients_list_item -->
<string name="message_recipients_list_item__resend">PONOVNO POŠALJI</string>
<!-- recipient_preferences -->
<string name="recipient_preferences__block">Blokiraj</string>
<!-- message_details_header -->
<string name="message_details_header__issues_need_your_attention">Neki problemi zahtjevaju tvoju pozornost.</string>
<string name="message_details_header__sent">Poslano</string>
<string name="message_details_header__received">Primljeno</string>
<string name="message_details_header__disappears">Nestaje</string>
<string name="message_details_header__via">Putem</string>
<string name="message_details_header__to">Prima:</string>
<string name="message_details_header__from">Šalje:</string>
<string name="message_details_header__with">Sa:</string>
<!-- AndroidManifest.xml -->
<string name="AndroidManifest__create_passphrase">Stvori lozinku</string>
<string name="AndroidManifest__select_contacts">Odaberite kontakte</string>
<string name="AndroidManifest__media_preview">Prikaz medija</string>
<!-- arrays.xml -->
<string name="arrays__use_default">Koristi zadano</string>
<string name="arrays__use_custom">Koristi prilagođeno</string>
<string name="arrays__mute_for_one_hour">Utišaj na 1 sat</string>
<string name="arrays__mute_for_two_hours">Utišaj na 2 sata</string>
<string name="arrays__mute_for_one_day">Utišaj na 1 dan</string>
<string name="arrays__mute_for_seven_days">Utišaj na 7 dana</string>
<string name="arrays__mute_for_one_year">Utišaj na 1 godinu</string>
<string name="arrays__settings_default">Zadana podešavanja</string>
<string name="arrays__enabled">Omogućeno</string>
<string name="arrays__disabled">Onemogućeno</string>
<string name="arrays__name_and_message">Ime i poruka</string>
<string name="arrays__name_only">Samo ime</string>
<string name="arrays__no_name_or_message">Nema imena ili poruke</string>
<string name="arrays__images">Slike</string>
<string name="arrays__audio">Audio</string>
<string name="arrays__video">Video</string>
<string name="arrays__documents">Dokumenti</string>
<string name="arrays__small">Mala</string>
<string name="arrays__normal">Normalna</string>
<string name="arrays__large">Velika</string>
<string name="arrays__extra_large">Ekstra velika</string>
<string name="arrays__default">Zadano</string>
<string name="arrays__high">Visoko</string>
<string name="arrays__max">Maks</string>
<!-- plurals.xml -->
<plurals name="hours_ago">
<item quantity="one">%d sat</item>
<item quantity="few">%d sata</item>
<item quantity="many">%d sati</item>
<item quantity="other">%d sati</item>
</plurals>
<!-- preferences.xml -->
<string name="preferences__pref_enter_sends_title">Enter šalje poruku</string>
<string name="preferences__pressing_the_enter_key_will_send_text_messages">Pritisak na Enter će poslati tekst poruku</string>
<string name="preferences__send_link_previews">Pošalji preglede linkova</string>
<string name="preferences__previews_are_supported_for">Pregledi su podržani za linkove Imgur, Instagram, Pinterest, Reddit i YouTube</string>
<string name="preferences__screen_security">Sigurnost ekrana</string>
<string name="preferences__disable_screen_security_to_allow_screen_shots">Onemogući snimanje ekrana na popisu nedavnih i unutar aplikacije</string>
<string name="preferences__notifications">Obavijesti</string>
<string name="preferences__led_color">LED boja</string>
<string name="preferences__led_color_unknown">Nepoznato</string>
<string name="preferences__pref_led_blink_title">LED uzorak treptanja</string>
<string name="preferences__sound">Zvuk</string>
<string name="preferences__silent">Bezvučno</string>
<string name="preferences__repeat_alerts">Ponovi upozorenja</string>
<string name="preferences__never">Nikada</string>
<string name="preferences__one_time">Jednom</string>
<string name="preferences__two_times">Dva puta</string>
<string name="preferences__three_times">Tri puta</string>
<string name="preferences__five_times">Pet puta</string>
<string name="preferences__ten_times">Deset puta</string>
<string name="preferences__vibrate">Vibracija</string>
<string name="preferences__green">Zelena</string>
<string name="preferences__red">Crvena</string>
<string name="preferences__blue">Plava</string>
<string name="preferences__orange">Narančasta</string>
<string name="preferences__cyan">Cijan</string>
<string name="preferences__magenta">Magenta</string>
<string name="preferences__white">Bijela</string>
<string name="preferences__none">Niti jedna</string>
<string name="preferences__fast">Brzo</string>
<string name="preferences__normal">Normalno</string>
<string name="preferences__slow">Sporo</string>
<string name="preferences__automatically_delete_older_messages_once_a_conversation_exceeds_a_specified_length">Automatski obriši starije poruke nakon što razgovor pređe određenu duljinu</string>
<string name="preferences__delete_old_messages">Obriši stare poruke</string>
<string name="preferences__conversation_length_limit">Maksimalna duljina razgovora</string>
<string name="preferences__trim_all_conversations_now">Skrati sve razgovore odmah</string>
<string name="preferences__scan_through_all_conversations_and_enforce_conversation_length_limits">Skeniraj sve razgovore i primijeni ograničenje duljine razgovora</string>
<string name="preferences__default">Zadano</string>
<string name="preferences__incognito_keyboard">Inkognito tipkovnica</string>
<string name="preferences__read_receipts">Potvrde čitanja</string>
<string name="preferences__if_read_receipts_are_disabled_you_wont_be_able_to_see_read_receipts">Ukoliko si onemogućio potvrdu čitanja kod sebe, nećeš moći vidjeti potvrde čitanja od drugih.</string>
<string name="preferences__typing_indicators">Pokazatelji tipkanja</string>
<string name="preferences__if_typing_indicators_are_disabled_you_wont_be_able_to_see_typing_indicators">Ako su indikatori kucanja onemogućeni, nećeš moći vidjeti indikatore kucanja od drugih.</string>
<string name="preferences__request_keyboard_to_disable_personalized_learning">Zahtijevaj od tipkovnice da onemogući personalizirano učenje</string>
<string name="preferences__light_theme">Svijetla</string>
<string name="preferences__dark_theme">Tamna</string>
<string name="preferences_chats__message_trimming">Skraćivanje poruke</string>
<string name="preferences_advanced__use_system_emoji">Koristi emotikone sustava</string>
<string name="preferences_advanced__disable_signal_built_in_emoji_support">Onemogući ugrađenu mECC podršku za emotikone</string>
<string name="preferences_app_protection__app_access">Pristup aplikaciji</string>
<string name="preferences_app_protection__communication">Komunikacija</string>
<string name="preferences_chats__chats">Razgovori</string>
<string name="preferences_notifications__messages">Poruke</string>
<string name="preferences_notifications__in_chat_sounds">Zvuk unutar razgovora</string>
<string name="preferences_notifications__show">Prikaži</string>
<string name="preferences_notifications__priority">Prioritet</string>
<!-- **************************************** -->
<!-- menus -->
<!-- **************************************** -->
<!-- contact_selection_list -->
<string name="contact_selection_list__unknown_contact">Nova poruka za...</string>
<!-- conversation_context -->
<string name="conversation_context__menu_message_details">Detalji poruke</string>
<string name="conversation_context__menu_copy_text">Kopiraj tekst</string>
<string name="conversation_context__menu_delete_message">Obriši poruku</string>
<string name="conversation_context__menu_ban_user">Banuj korisnika</string>
<string name="conversation_context__menu_resend_message">Ponovno pošalji poruku</string>
<string name="conversation_context__menu_reply_to_message">Odgovori na poruku</string>
<!-- conversation_context_image -->
<string name="conversation_context_image__save_attachment">Spremi privitak</string>
<!-- conversation_expiring_off -->
<string name="conversation_expiring_off__disappearing_messages">Nestajuće poruke</string>
<!-- conversation_expiring_on -->
<string name="menu_conversation_expiring_on__messages_expiring">Poruke ističu</string>
<!-- conversation_muted -->
<string name="conversation_muted__unmute">Ukloni utišanje</string>
<!-- conversation_unmuted -->
<string name="conversation_unmuted__mute_notifications">Utišaj obavijesti</string>
<!-- conversation -->
<string name="conversation__menu_edit_group">Uredi grupu</string>
<string name="conversation__menu_leave_group">Napusti grupu</string>
<string name="conversation__menu_view_all_media">Svi mediji</string>
<string name="conversation__menu_add_shortcut">Dodaj na početni ekran</string>
<!-- conversation_popup -->
<string name="conversation_popup__menu_expand_popup">Proširi skočni prozor</string>
<!-- conversation_group_options -->
<string name="conversation_group_options__delivery">Isporuka</string>
<string name="conversation_group_options__conversation">Razgovor</string>
<string name="conversation_group_options__broadcast">Emitiranje</string>
<!-- media_preview -->
<string name="media_preview__save_title">Spremi</string>
<string name="media_preview__forward_title">Prosljedi</string>
<string name="media_preview__all_media_title">Svi mediji</string>
<!-- media_overview -->
<string name="media_overview_documents_fragment__no_documents_found">Nema dokumenata</string>
<!-- media_preview_activity -->
<string name="media_preview_activity__media_content_description">Prikaz medija</string>
<!-- Trimmer -->
<string name="trimmer__deleting">Brisanje</string>
<string name="trimmer__deleting_old_messages">Brisanje starih poruka...</string>
<string name="trimmer__old_messages_successfully_deleted">Stare poruke su uspješno obrisane</string>
<!-- transport_selection_list_item -->
<string name="Permissions_permission_required">Potrebna dozvola</string>
<string name="Permissions_continue">Nastavi</string>
<string name="Permissions_not_now">Ne sada</string>
<string name="backup_enable_dialog__backups_will_be_saved_to_external_storage_and_encrypted_with_the_passphrase_below_you_must_have_this_passphrase_in_order_to_restore_a_backup">Sigurnosne kopije bit će spremljene u vanjsku pohranu i šifrirane donjom šifrom. Moraš imati ovu šifru da bi vratio sigurnosnu kopiju.</string>
<string name="backup_enable_dialog__i_have_written_down_this_passphrase">Zapisao sam ovu pristupnu frazu. Bez toga neću moći da vratim rezervnu kopiju.</string>
<string name="registration_activity__skip">Preskoči</string>
<string name="RegistrationActivity_backup_failure_downgrade">Ne mogu da se uvezu rezervne kopije iz novijih verzija mECC</string>
<string name="RegistrationActivity_incorrect_backup_passphrase">Pogrešna šifra za sigurnosnu kopiju</string>
<string name="BackupDialog_enable_local_backups">Omogućiti lokalne sigurnosne kopije?</string>
<string name="BackupDialog_enable_backups">Omogući sigurnosne kopije</string>
<string name="BackupDialog_please_acknowledge_your_understanding_by_marking_the_confirmation_check_box">Potvrdi svoje razumijevanje označavanjem polja za potvrdu.</string>
<string name="BackupDialog_delete_backups">Izbrisati sigurnosne kopije?</string>
<string name="BackupDialog_disable_and_delete_all_local_backups">Onemogućiti i izbrisati sve lokalne sigurnosne kopije?</string>
<string name="BackupDialog_delete_backups_statement">Izbriši sigurnosne kopije</string>
<string name="BackupDialog_copied_to_clipboard">Kopirano u međuspremnik</string>
<string name="LocalBackupJob_creating_backup">Stvaranje sigurnosne kopije...</string>
<string name="ProgressPreference_d_messages_so_far">%d poruka do sada</string>
<string name="BackupUtil_never">Nikada</string>
<string name="preferences_app_protection__screen_lock">Zaključavanje ekrana</string>
<string name="preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint">Zaključaj pristup mECC-u pomoću Android zaključavanja ekrana ili otiska prsta</string>
<string name="preferences_app_protection__screen_lock_inactivity_timeout">Vrijeme neaktivnosti za zaključavanja ekrana</string>
<string name="AppProtectionPreferenceFragment_none">Niti jedna</string>
<!-- Conversation activity -->
<string name="activity_conversation_copy_public_key_button_title">Kopiraj javni ključ</string>
<!-- Session -->
<string name="continue_2">Nastavi</string>
<string name="copy">Kopiraj</string>
<string name="invalid_url">Nevažeći URL</string>
<string name="copied_to_clipboard">Kopirano u međuspremnik</string>
<string name="next">Sledeći</string>
<string name="share">Podjeli</string>
<string name="invalid_session_id">Nevažeci mECC ID</string>
<string name="cancel">Otkaži</string>
<string name="your_session_id">Tvoj mECC ID</string>
<string name="activity_landing_title_2">Tvoj mECC počinje ovdje...</string>
<string name="activity_landing_register_button_title">Napravi mECC ID</string>
<string name="activity_landing_restore_button_title">Nastavi svoj mECC</string>
<string name="view_fake_chat_bubble_1">Šta je mECC?</string>
<string name="view_fake_chat_bubble_2">Decentralizirana, enkriptovana aplikacija za razmjenu poruka</string>
<string name="view_fake_chat_bubble_3">Dakle, ne prikuplja moje lične podatke ili metapodatke razgovora? Kako to radi?</string>
<string name="view_fake_chat_bubble_4">Koristeći kombinaciju naprednog anonimnog usmjeravanja i end-to-end tehnologije šifriranja. (Ne pitaj puno)</string>
<string name="view_fake_chat_bubble_5">Prijatelji ne dopuštaju prijateljima da koriste kompromitirane aplikacije. Nema na čemu.</string>
<string name="activity_register_title">Pozdravi svoj mECC ID</string>
<string name="activity_register_explanation">Tvoj mECC ID je jedinstvena adresa koju ljudi mogu koristiti da te kontaktiraju na mECC-u. Bez poveznice sa tvojim stvarnim identitetom, tvoj mECC ID je potpuno anoniman i dizajniran da bude privatan.</string>
<string name="activity_restore_title">Vrati svoj račun</string>
<string name="activity_restore_explanation">Unesi frazu za oporavak koja ti je dana kada si se registrovao prvi put da bi vratio račun.</string>
<string name="activity_restore_seed_edit_text_hint">Unesite frazu za oporavak</string>
<string name="activity_display_name_title_2">Izaberi svoj nadimak</string>
<string name="activity_display_name_explanation">Ovo će biti tvoje ime kada budeš koristili mECC. To može biti vaše pravo ime (Nije preporučljivo), pseudonim ili bilo šta drugo što želiš.</string>
<string name="activity_display_name_edit_text_hint">Unesi nadimak</string>
<string name="activity_display_name_display_name_missing_error">Odaberi nadimak</string>
<string name="activity_display_name_display_name_too_long_error">Odaberi kraći nadimak</string>
<string name="activity_pn_mode_recommended_option_tag">Preporučeno</string>
<string name="activity_pn_mode_no_option_picked_dialog_title">Odaberi jednu opciu</string>
<string name="activity_home_empty_state_message">Još nemaš nijedan kontakt</string>
<string name="activity_home_empty_state_button_title">Pokreni mECC</string>
<string name="activity_home_leave_group_dialog_message">Jesi li siguran da želiš napustiti ovu grupu?</string>
<string name="activity_home_leaving_group_failed_message">"Napuštanje grupe nije uspjelo"</string>
<string name="activity_home_delete_conversation_dialog_message">Jesi li siguran da želiš izbrisati ovaj razgovor?</string>
<string name="activity_home_conversation_deleted_message">Razgovor je izbrisan</string>
<string name="activity_seed_title">Tvoja fraza za oporavak</string>
<string name="activity_seed_title_2">Upoznajte svoju frazu za oporavak</string>
<string name="activity_seed_explanation">Tvoja fraza za oporavak glavni je ključ tvog mECC ID-a - možeš ga koristiti za vraćanje ID-a mECC-a ako izgubiš pristup uređaju. Spremi frazu za oporavak na sigurno mjesto i ne daj je nikome.</string>
<string name="activity_seed_reveal_button_title">Drži da otkriješ</string>
<string name="view_seed_reminder_title">Skoro si gotov! 80%</string>
<string name="view_seed_reminder_subtitle_1">Osigurajte svoj račun spremanjem fraze za oporavak</string>
<string name="view_seed_reminder_subtitle_2">Dodirni i zadrži uređene riječi da bi otkrio frazu za oporavak, a zatim je sigurno spremi kako biste osigurao svoj mECC ID.</string>
<string name="view_seed_reminder_subtitle_3">Obavezno pohranite frazu za oporavak na sigurno mjesto (Mojne da ti to padne u pogrešne ruke)</string>
<string name="activity_path_title">Putanja</string>
<string name="activity_path_explanation">mECC sakriva tvoj IP odbijanjem vaših poruka kroz nekoliko servisnih node-ova u decentraliziranoj mreži mCCA-a. Ne boj se jarane valja ovo. Ovo su zemlje kroz koje se tvoja veza trenutno odbija:</string>
<string name="activity_path_device_row_title">Ti</string>
<string name="activity_path_guard_node_row_title">Ulazni Node</string>
<string name="activity_path_service_node_row_title">Servisni Node</string>
<string name="activity_path_destination_row_title">Destinacija</string>
<string name="activity_path_learn_more_button_title">Naučiti više</string>
<string name="activity_create_private_chat_title">Novi mECC</string>
<string name="activity_create_private_chat_enter_session_id_tab_title">Ukucaj mECC ID</string>
<string name="activity_create_private_chat_scan_qr_code_tab_title">Skeniraj QR Kod</string>
<string name="activity_create_private_chat_scan_qr_code_explanation">Skeniraj QR kod korisnika da biste započeli link. QR kodovi se mogu pronaći dodirom ikone QR koda u podešavanjima računa.</string>
<string name="fragment_enter_public_key_explanation">Korisnici mogu podijeliti svoj mECC ID tako što će otići u postavke svog računa i dodirnuti \"Podjeli mECC ID\" ili dijeljenjem svog QR koda.</string>
<string name="fragment_scan_qr_code_camera_access_explanation">mECC treba pristup kamere za skeniranje QR kodova</string>
<string name="fragment_scan_qr_code_grant_camera_access_button_title">Odobri pristup kameri</string>
<string name="activity_create_closed_group_title">Nova zatvorena grupa</string>
<string name="activity_create_closed_group_edit_text_hint">Unesi naziv grupe</string>
<string name="activity_create_closed_group_empty_state_message">Još nemaš nijedan kontakt</string>
<string name="activity_create_closed_group_empty_state_button_title">Pokreni mECC</string>
<string name="activity_create_closed_group_group_name_missing_error">Unesi naziv grupe</string>
<string name="activity_create_closed_group_group_name_too_long_error">Unesi kraće ime grupe</string>
<string name="activity_create_closed_group_not_enough_group_members_error">Odaberi najmanje 1 člana grupe</string>
<string name="activity_create_closed_group_too_many_group_members_error">Zatvorena grupa ne može imati više od 100 članova</string>
<string name="activity_join_public_chat_title">Pridruži se otvorenoj grupi</string>
<string name="activity_join_public_chat_error">Pridruživanje grupi nije uspjelo</string>
<string name="activity_join_public_chat_enter_group_url_tab_title">Otvori URL Grupe</string>
<string name="activity_join_public_chat_scan_qr_code_tab_title">Skeniraj QR Kod</string>
<string name="activity_join_public_chat_scan_qr_code_explanation">Skeniraj QR kod otvorene grupe kojoj se želiš pridružiti</string>
<string name="fragment_enter_chat_url_edit_text_hint">Unesi URL otvorene grupe</string>
<string name="activity_settings_title">Podešavanja</string>
<string name="activity_settings_display_name_edit_text_hint">Unesite mECC nadimak</string>
<string name="activity_settings_display_name_missing_error">Odaberi mECC nadimak</string>
<string name="activity_settings_display_name_too_long_error">Odaberi kraći mECC nadimak</string>
<string name="activity_settings_privacy_button_title">Privatnost</string>
<string name="activity_settings_notifications_button_title">Notifikacije</string>
<string name="activity_settings_chats_button_title">Razgovori</string>
<string name="activity_settings_devices_button_title">Uređaji</string>
<string name="activity_settings_recovery_phrase_button_title">Fraza za oporavak</string>
<string name="activity_settings_clear_all_data_button_title">Obriši podatke</string>
<string name="activity_settings_help_translate_session">UPDATE</string>
<string name="activity_notification_settings_title">Notifikacije</string>
<string name="activity_notification_settings_style_section_title">Stil Notifikacija</string>
<string name="activity_notification_settings_content_section_title">Sadržaj notifikacija</string>
<string name="activity_privacy_settings_title">Privatnost</string>
<string name="activity_chat_settings_title">Razgovori</string>
<string name="preferences_notifications_strategy_category_title">Strategija Notifikacija</string>
<string name="fragment_device_list_bottom_sheet_change_name_button_title">Promjeni ime</string>
<string name="fragment_device_list_bottom_sheet_unlink_device_button_title">Odveži uređaj</string>
<string name="dialog_seed_title">Tvoja fraza za oporavak</string>
<string name="dialog_seed_explanation">Ovo je tvoja fraza za oporavak. Pomoću nje možeš vratiti ili migrirati svoj mECC ID na novi uređaj.</string>
<string name="dialog_clear_all_data_title">Obriši sve Podatke</string>
<string name="dialog_clear_all_data_explanation">Ovo će trajno izbrisati tvoje poruke, mECC ID-e i kontakte.</string>
<string name="activity_qr_code_title">QR Kod</string>
<string name="activity_qr_code_view_my_qr_code_tab_title">Prikaži moj QR Kod</string>
<string name="activity_qr_code_view_scan_qr_code_tab_title">Skeniraj QR Kod</string>
<string name="activity_qr_code_view_scan_qr_code_explanation">Skeniraj nečiji QR kod da započneš razgovor s njim</string>
<string name="fragment_view_my_qr_code_title">Skeniraj ME</string>
<string name="fragment_view_my_qr_code_explanation">Ovo je tvoj QR kod. Drugi korisnici mogu da ga skeniraju kako bi započeli mECC link s tobom.</string>
<string name="fragment_view_my_qr_code_share_title">Podjeli QR Kod</string>
<string name="fragment_contact_selection_contacts_title">Kontakti</string>
<string name="fragment_contact_selection_closed_groups_title">Zatvorene Grupe</string>
<string name="fragment_contact_selection_open_groups_title">Otvorene Grupe</string>
<string name="fragment_contact_selection_empty_contacts">Još nemaš nijedan kontakt</string>
<!-- Next round of translation -->
<string name="menu_apply_button">Primjeni</string>
<string name="menu_done_button">Završeno</string>
<string name="activity_edit_closed_group_title">Uredi grupu</string>
<string name="activity_edit_closed_group_edit_text_hint">Unesi novi naziv grupe</string>
<string name="activity_edit_closed_group_edit_members">Članovi</string>
<string name="activity_edit_closed_group_add_members">Dodaj članove</string>
<string name="activity_edit_closed_group_group_name_missing_error">Ime grupe ne može biti prazno</string>
<string name="activity_edit_closed_group_group_name_too_long_error">Unesi kraće ime grupe</string>
<string name="activity_edit_closed_group_not_enough_group_members_error">Grupe moraju imati najmanje 1 člana</string>
<string name="fragment_edit_group_bottom_sheet_remove">Ukloni korisnika iz grupe</string>
<string name="activity_select_contacts_title">Odaberi kontakte</string>
<string name="view_reset_secure_session_done_message">Sigurnosno resetiranje mECC-a obavljeno</string>
<string name="dialog_ui_mode_title">Tema</string>
<string name="dialog_ui_mode_option_day">Dan</string>
<string name="dialog_ui_mode_option_night">Noć</string>
<string name="dialog_ui_mode_option_system_default">Sistemski zadana podešavanja</string>
<string name="activity_conversation_menu_copy_session_id">Kopiraj vaš session ID</string>
<string name="attachment">Prilog</string>
<string name="attachment_type_voice_message">Glasovna Poruka</string>
<string name="details">Detalji</string>
<string name="dialog_backup_activation_failed">Aktiviranje sigurnosnih kopija nije uspjelo. Pokušaj ponovo ili kontaktirajte administratora za podršku.</string>
<string name="activity_backup_restore_title">Vrati sigurnosnu kopiju</string>
<string name="activity_backup_restore_select_file">Odaberi datoteku</string>
<string name="activity_backup_restore_explanation_1">Odaberi datoteku sigurnosne kopije i unesi šifru pomoću koje je kreirana.</string>
<string name="activity_backup_restore_passphrase">30-cifrena šifra</string>
<string name="activity_link_device_skip_prompt">Ovo traje neko vrijeme, želiš li preskočiti?</string>
<string name="activity_link_device_link_device">Poveži uređaj</string>
<string name="activity_join_public_chat_join_rooms">Ili se pridruži jednom od ovih…</string>
<string name="activity_pn_mode_message_notifications">Obavijesti o porukama</string>
<string name="activity_pn_mode_explanation">Postoje dva načina na koje te mECC može obavijestiti o novim porukama.</string>
<string name="activity_pn_mode_fast_mode">Brzi režim</string>
<string name="activity_pn_mode_slow_mode">Spori režim</string>
<string name="activity_pn_mode_fast_mode_explanation">Obavjesti za nove poruke dobit ćeš pouzdano i odmah pomoću nekih jebenih servera za obavijesti.</string>
<string name="activity_pn_mode_slow_mode_explanation">mECC će povremeno provjeravati ima li novih poruka u pozadini.</string>
<string name="fragment_recovery_phrase_title">Fraza za oporavak</string>
<string name="activity_prompt_passphrase_session_locked">mECC je zaključan</string>
<string name="activity_prompt_passphrase_tap_to_unlock">Dodirni za otključavanje</string>
<string name="fragment_user_details_bottom_sheet_edit_text_hint">Unesi nadimak</string>
<string name="invalid_public_key">Nevažeći javni ključ</string>
</resources>

View File

@@ -70,7 +70,7 @@
android:summary="@string/preferences__if_read_receipts_are_disabled_you_wont_be_able_to_see_read_receipts"/>
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="true"
android:defaultValue="false"
android:key="pref_typing_indicators"
android:title="@string/preferences__typing_indicators"
android:summary="@string/preferences__if_typing_indicators_are_disabled_you_wont_be_able_to_see_typing_indicators"/>

View File

@@ -5,6 +5,7 @@ import android.net.Uri
import org.session.libsession.messaging.calls.CallMessageType
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.jobs.AttachmentUploadJob
import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob
import org.session.libsession.messaging.jobs.Job
import org.session.libsession.messaging.jobs.MessageSendJob
import org.session.libsession.messaging.messages.control.ConfigurationMessage
@@ -44,6 +45,7 @@ interface StorageProtocol {
fun getAttachmentUploadJob(attachmentID: Long): AttachmentUploadJob?
fun getMessageSendJob(messageSendJobID: String): MessageSendJob?
fun getMessageReceiveJob(messageReceiveJobID: String): Job?
fun getGroupAvatarDownloadJob(server: String, room: String): Job?
fun resumeMessageSendJobIfNeeded(messageSendJobID: String)
fun isJobCanceled(job: Job): Boolean
@@ -118,6 +120,7 @@ interface StorageProtocol {
fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList<ECKeyPair>
fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair?
fun updateFormationTimestamp(groupID: String, formationTimestamp: Long)
fun updateTimestampUpdated(groupID: String, updatedTimestamp: Long)
fun setExpirationTimer(groupID: String, duration: Int)
// Groups

View File

@@ -0,0 +1,54 @@
package org.session.libsession.messaging.jobs
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.messaging.utilities.Data
import org.session.libsession.utilities.GroupUtil
class GroupAvatarDownloadJob(val room: String, val server: String) : Job {
override var delegate: JobDelegate? = null
override var id: String? = null
override var failureCount: Int = 0
override val maxFailureCount: Int = 10
override fun execute() {
val storage = MessagingModuleConfiguration.shared.storage
try {
val info = OpenGroupAPIV2.getInfo(room, server).get()
val bytes = OpenGroupAPIV2.downloadOpenGroupProfilePicture(info.id, server).get()
val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray())
storage.updateProfilePicture(groupId, bytes)
storage.updateTimestampUpdated(groupId, System.currentTimeMillis())
delegate?.handleJobSucceeded(this)
} catch (e: Exception) {
delegate?.handleJobFailed(this, e)
}
}
override fun serialize(): Data {
return Data.Builder()
.putString(ROOM, room)
.putString(SERVER, server)
.build()
}
override fun getFactoryKey(): String = KEY
companion object {
const val KEY = "GroupAvatarDownloadJob"
private const val ROOM = "room"
private const val SERVER = "server"
}
class Factory : Job.Factory<GroupAvatarDownloadJob> {
override fun create(data: Data): GroupAvatarDownloadJob {
return GroupAvatarDownloadJob(
data.getString(ROOM),
data.getString(SERVER)
)
}
}
}

View File

@@ -45,9 +45,16 @@ class JobQueue : JobDelegate {
while (isActive) {
for (job in queue) {
when (job) {
is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob -> txQueue.send(job)
is MessageReceiveJob, is TrimThreadJob, is BatchMessageReceiveJob, is AttachmentDownloadJob-> rxQueue.send(job)
else -> throw IllegalStateException("Unexpected job type.")
is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob -> {
txQueue.send(job)
}
is MessageReceiveJob, is TrimThreadJob, is BatchMessageReceiveJob,
is AttachmentDownloadJob, is GroupAvatarDownloadJob -> {
rxQueue.send(job)
}
else -> {
throw IllegalStateException("Unexpected job type.")
}
}
}
}
@@ -123,7 +130,8 @@ class JobQueue : JobDelegate {
MessageReceiveJob.KEY,
MessageSendJob.KEY,
NotifyPNServerJob.KEY,
BatchMessageReceiveJob.KEY
BatchMessageReceiveJob.KEY,
GroupAvatarDownloadJob.KEY
)
allJobTypes.forEach { type ->
resumePendingJobs(type)

View File

@@ -12,7 +12,8 @@ class SessionJobManagerFactories {
MessageSendJob.KEY to MessageSendJob.Factory(),
NotifyPNServerJob.KEY to NotifyPNServerJob.Factory(),
TrimThreadJob.KEY to TrimThreadJob.Factory(),
BatchMessageReceiveJob.KEY to BatchMessageReceiveJob.Factory()
BatchMessageReceiveJob.KEY to BatchMessageReceiveJob.Factory(),
GroupAvatarDownloadJob.KEY to GroupAvatarDownloadJob.Factory()
)
}
}

View File

@@ -40,6 +40,7 @@ class OpenGroupPollerV2(private val server: String, private val executorService:
fun poll(isBackgroundPoll: Boolean = false): Promise<Unit, Exception> {
val storage = MessagingModuleConfiguration.shared.storage
val rooms = storage.getAllV2OpenGroups().values.filter { it.server == server }.map { it.room }
rooms.forEach { downloadGroupAvatarIfNeeded(it) }
return OpenGroupAPIV2.compactPoll(rooms, server).successBackground { responses ->
responses.forEach { (room, response) ->
val openGroupID = "$server.$room"
@@ -50,7 +51,7 @@ class OpenGroupPollerV2(private val server: String, private val executorService:
}
}
}.always {
executorService?.schedule(this@OpenGroupPollerV2::poll, OpenGroupPollerV2.pollInterval, TimeUnit.MILLISECONDS)
executorService?.schedule(this@OpenGroupPollerV2::poll, pollInterval, TimeUnit.MILLISECONDS)
}.map { }
}
@@ -103,4 +104,15 @@ class OpenGroupPollerV2(private val server: String, private val executorService:
storage.setLastDeletionServerID(room, server, latestMax)
}
}
private fun downloadGroupAvatarIfNeeded(room: String) {
val storage = MessagingModuleConfiguration.shared.storage
if (storage.getGroupAvatarDownloadJob(server, room) != null) return
val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray())
storage.getGroup(groupId)?.let {
if (System.currentTimeMillis() > it.updatedTimestamp + TimeUnit.DAYS.toMillis(7)) {
JobQueue.shared.add(GroupAvatarDownloadJob(room, server))
}
}
}
}

View File

@@ -1,15 +1,14 @@
package org.session.libsession.utilities
import android.text.TextUtils
import org.session.libsession.utilities.Address
import java.io.IOException
import java.util.*
import java.util.LinkedList
class GroupRecord(
val encodedId: String, val title: String, members: String?, val avatar: ByteArray?,
val avatarId: Long?, val avatarKey: ByteArray?, val avatarContentType: String?,
val relay: String?, val isActive: Boolean, val avatarDigest: ByteArray?, val isMms: Boolean,
val url: String?, admins: String?, val formationTimestamp: Long
val url: String?, admins: String?, val formationTimestamp: Long, val updatedTimestamp: Long
) {
var members: List<Address> = LinkedList<Address>()
var admins: List<Address> = LinkedList<Address>()