Merge branch 'develop' into feature/fga/fix_some_voip_issues

This commit is contained in:
ganfra 2021-02-16 16:06:12 +01:00
commit 776ebce497
298 changed files with 2637 additions and 1090 deletions

View file

@ -8,10 +8,12 @@ Improvements 🙌:
- VoIP : new tiles in timeline
- Improve room profile UX
- Upgrade Jitsi library from 2.9.3 to 3.1.0
- a11y improvements
Bugfix 🐛:
- VoIP : fix audio devices output
- Fix crash after initial sync on Dendrite
- Fix crash reported by PlayStore (#2707)
Translations 🗣:
-
@ -26,6 +28,7 @@ Test:
-
Other changes:
- New Dev Tools panel for developers
- Fix typos in CHANGES.md (#2811)
Changes in Element 1.0.17 (2021-02-09)

View file

@ -0,0 +1,2 @@
يحتوي هذا الإصدار الجديد بشكل أساسي على إصلاحات للأخطاء وتحسينات. إرسال الرسالة أصبح الآن أسرع بكثير.
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.10

View file

@ -0,0 +1,2 @@
يحتوي هذا الإصدار الجديد بشكل أساسي على تحسينات في واجهة المستخدم وتجربة المستخدم. يُمكنك الآن دعوة الأصدقاء وإنشاء رسالة مُباشرة بسرعة كبيرة عن طريق مسح رموز الاستجابة السريعة.
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.11

View file

@ -0,0 +1,2 @@
التغييرات الرئيسة في هذا الإصدار: مُعاينة URL، لوحة مفاتيح Emoji جديدة، إمكانيات جديدة لإعدادات الغرفة والثلج لميلاد المسيح!
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.12

View file

@ -0,0 +1,2 @@
التغييرات الرئيسة في هذا الإصدار: مُعاينة URL، لوحة مفاتيح Emoji جديدة، إمكانيات جديدة لإعدادات الغرفة والثلج لميلاد المسيح!
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
التغييرات الرئيسة في هذا الإصدار: تحرير أذونات الغُرفة، السِّمة التلقائية الفاتحة/الداكنة، ومجموعة من إصلاحات الأخطاء.
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
التغييرات الرئيسة في هذا الإصدار: دعم تسجيل الدخول الاجتماعي.
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
التغييرات الرئيسة في هذا الإصدار: دعم تسجيل الدخول الاجتماعي.
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -0,0 +1,2 @@
التغييرات الرئيسة في هذا الإصدار: إصلاحات الأخطاء!
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -0,0 +1,31 @@
Element هو نوع جديد من تطبيقات المُراسلة والتعاون الذي:
1. يمنحك التحكم في المُحافضة على خصوصيتك
2. يُتيح لك التواصل مع أي شخص على شبكة Matrix ، وحتى خارجها من خلال التكامل مع التطبيقات مثل Slack
3. يحميك من الإعلانات والتنقيب عن البيانات وعمليات الحدائق المُسورة
4. يؤمنك من خلال تعمية النهاية-إلى-النهاية، مع التوقيع المُتبادل للتحقق من الآخرين
يختلف Element تمامًا عن تطبيقات المُراسلة والتعاون الأُخرى لأنه لا مركزي ومفتوح المصدر.
يُتيح لك Element إمكانية الاستضافة الذاتية -أو اختيار مُضيف- بحيث تتمتع بالخصوصية والمُلكية والتحكم في بياناتك ومُحادثاتك. يُتيح لك الوصول إلى شبكة مفتوحة؛ لذلك لا يقتصر الأمر على التحدث إلى مستخدمي Element الآخرين فقط. كما انه آمن للغاية.
Element قادر على القيام بكل ذلك لأنه يعمل على Matrix -مِعيار التواصل المفتوح اللامركزي.
Element يمنحك زمام التحكم من خلال السماح لك باختيار من يستضيف المُحادثات الخاصة بك. من تطبيق Element، يُمكنك اختيار الاستضافة بطرق مختلفة:
1. الحُصول على حساب مجاني على الخادِم العام matrix.org الذي يستضيفه مطورو Matrix، أو اختر من بين آلاف الخوادِم العامة التي يستضيفها متطوعون
2. استضافة حسابك بنفسك عن طريق تشغيل خادِم على أجهزتك الخاصة
3. التسجيل للحصول على حساب على خادِم مُخصص بمُجرد الاشتراك في منصة استضافة Element Matrix Services
<b> لماذا تختار Element؟</b>
<b>تملَّك بياناتك</b>: أنت من تُقرر أين تحتفظ ببياناتك ورسائلك. أنت تمتلكها وتتحكم فيها، وليس بعض الشركات الكُبرى الإحتكارية التي تُنقِّب عن بياناتك أو تُتيح الوصول إلى أطراف ثالثة.
<b>تراسُل وتعاون مفتوح</b>: يُمكنك مُحادثة أي شخص آخر على شبكة Matrix، سواء كانوا يستخدمون Element أو تطبيق Matrix آخر، وحتى إذا كانوا يستخدمون نظام مُراسلة مُختلف مثل Slack أو IRC أو XMPP.
<b>الأمان-الخارق</b>: تشفير حقيقي من النهاية إلى النهاية (فقط أطراف المُحادثة مَن يُمكنهم فك تشفير الرسائل)، والتوقيع المُتبادل للتحقق من أجهزة المُشاركين في المُحادثة.
<b>التواصل الكامل</b>: المُراسلة، المُكالمات الصوتية والمرئية، مُشاركة الملفات، مُشاركة الشاشة، مجموعة كاملة وكبيرة من عمليات التكامُل، الروبوتات والأدوات. بناء الغُرف، المُجتمعات، ابق على اتصال وأنجز المهام.
<b>أين ما كُنت</b>: ابق على اتصال أينما كنت مع سجل الرسائل المتزامن بالكامل عبر جميع أجهزتك وفي الويب على https://app.element.io.

View file

@ -0,0 +1 @@
مُحادثة آمنة لا مركزية و VoIP. حافظ على بياناتك آمنة من الأطراف الثالثة.

View file

@ -0,0 +1 @@
Element (سابقاً Riot.im)

View file

@ -0,0 +1,2 @@
Canvis principals d'aquesta versió: correcció d'errors!
Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -0,0 +1,2 @@
Hauptänderungen in dieser Version: Fehlerkorrekturen
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -0,0 +1,2 @@
Olulisemad muutused selles versioonis: Veaparandused!
Muudatuste logi täismahus: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -1,2 +1,2 @@
Modifiche principali in questa versione: anteprima URL, nuova tastiera emoji, nuove impostazioni stanza e neve per Natale!
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.0.12
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
Modifiche principali in questa versione: modifica autorizzazioni stanza, tema chiaro/scuro automatico e varie correzioni di errori.
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
Modifiche principali in questa versione: supporto all'accesso dai social.
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
Modifiche principali in questa versione: supporto all'accesso dai social.
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -0,0 +1,2 @@
Modifiche principali in questa versione: correzioni di errori!
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: Ukážka URL, nová klávesnica Emoji, nové možnosti nastavenia miestnosti a sneh na Vianoce!
Celý zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.0.12

View file

@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: Ukážka URL, nová klávesnica Emoji, nové možnosti nastavenia miestnosti a sneh na Vianoce!
Celý zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: Úpravy povolení miestnosti, automatický svetlý / tmavý motív a veľa opráv chýb.
Celý zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: Podpora sociálneho prihlásenia.
Celý zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: Podpora sociálneho prihlásenia.
Celý zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: Opravy chýb!
Celý zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -1 +1 @@
Zabezpečené konverzácie a VoIP. Ochráňte vaše údaje pred zhromažďovaním.
Zabezpečené konverzácie a VoIP. Ochráňte vaše údaje pred tretími stranami.

View file

@ -0,0 +1,2 @@
Главна измена у овој верзији: сређене грешке!
Цео дневник измена: https://github.com/vector-im/element-android/releases/tag/v1.0.15 и https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -0,0 +1,2 @@
Основні зміни у цій версії: Виправлення помилок!
Повний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -30,24 +30,24 @@ data class RoomThirdPartyInviteContent(
* This should not contain the user's third party ID, as otherwise when the invite
* is accepted it would leak the association between the matrix ID and the third party ID.
*/
@Json(name = "display_name") val displayName: String,
@Json(name = "display_name") val displayName: String?,
/**
* Required. A URL which can be fetched, with querystring public_key=public_key, to validate
* whether the key has been revoked. The URL must return a JSON object containing a boolean property named 'valid'.
*/
@Json(name = "key_validity_url") val keyValidityUrl: String,
@Json(name = "key_validity_url") val keyValidityUrl: String?,
/**
* Required. A base64-encoded ed25519 key with which token must be signed (though a signature from any entry in
* public_keys is also sufficient). This exists for backwards compatibility.
*/
@Json(name = "public_key") val publicKey: String,
@Json(name = "public_key") val publicKey: String?,
/**
* Keys with which the token may be signed.
*/
@Json(name = "public_keys") val publicKeys: List<PublicKeys> = emptyList()
@Json(name = "public_keys") val publicKeys: List<PublicKeys>? = emptyList()
)
@JsonClass(generateAdapter = true)

View file

@ -65,13 +65,30 @@ interface StateService {
*/
suspend fun deleteAvatar()
/**
* Send a state event to the room
*/
suspend fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict)
/**
* Get a state event of the room
*/
fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
/**
* Get a live state event of the room
*/
fun getStateEventLive(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<Optional<Event>>
/**
* Get state events of the room
* @param eventTypes Set of eventType. If empty, all state events will be returned
*/
fun getStateEvents(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): List<Event>
/**
* Get live state events of the room
* @param eventTypes Set of eventType to observe. If empty, all state events will be observed
*/
fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>>
}

View file

@ -196,6 +196,7 @@ internal class EventSenderProcessor @Inject constructor(
else -> {
Timber.v("## SendThread retryLoop Un-Retryable error, try next task")
// this task is in error, check next one?
task.onTaskFailed()
break@retryLoop
}
}

View file

@ -80,7 +80,11 @@ internal class StateEventDataSource @Inject constructor(@SessionDatabase private
): RealmQuery<CurrentStateEventEntity> {
return realm.where<CurrentStateEventEntity>()
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
.`in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray())
.apply {
if (eventTypes.isNotEmpty()) {
`in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray())
}
}
.process(CurrentStateEventEntityFields.STATE_KEY, stateKey)
}
}

View file

@ -1,147 +1,150 @@
<?xml version='1.0' encoding='UTF-8'?>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="summary_user_sent_image">أرسل %1$s صورة.</string>
<string name="notice_room_invite_no_invitee">دعوة من %s</string>
<string name="notice_room_invite">دعى %1$s %2$s</string>
<string name="notice_room_invite_you">دعاك %1$s</string>
<string name="notice_room_join">انضمّ %1$s إلى الغرفة</string>
<string name="notice_room_leave">غادر %1$s الغرفة</string>
<string name="notice_room_reject">رفض %1$s الدعوة</string>
<string name="notice_room_kick">طرد %1$s %2$s</string>
<string name="notice_room_unban">رفع %1$s المنع عن %2$s</string>
<string name="notice_room_ban">منع %1$s %2$s</string>
<string name="notice_avatar_url_changed">غيّر %1$s صورته</string>
<string name="notice_display_name_set">ضبط %1$s اسم العرض على %2$s</string>
<string name="notice_display_name_changed_from">غيّر %1$s اسم العرض من %2$s إلى %3$s</string>
<string name="notice_display_name_removed">أزال %1$s اسم العرض (⁨كان %2$s)</string>
<string name="notice_room_topic_changed">غيّر %1$s الموضوع إلى: %2$s</string>
<string name="notice_room_name_changed">غيّر %1$s اسم الغرفة إلى: %2$s</string>
<string name="notice_answered_call">ردّ %s على المكالمة.</string>
<string name="notice_ended_call">أنهى %s المكالمة.</string>
<string name="notice_made_future_room_visibility">جعل %1$s تأريخ الغرفة مستقبلًا ظاهرًا على %2$s</string>
<string name="notice_room_visibility_invited">كل أعضاء الغرفة من لحظة دعوتهم.</string>
<string name="notice_room_visibility_joined">كل أعضاء الغرفة من لحظة انضمامهم.</string>
<string name="notice_room_visibility_shared">كل أعضاء الغرفة.</string>
<string name="notice_room_visibility_world_readable">الكل.</string>
<string name="notice_room_visibility_unknown">المجهول (%s).</string>
<string name="notice_end_to_end">فعّل %1$s تعمية الطرفين (%2$s)</string>
<string name="notice_requested_voip_conference">طلب %1$s اجتماع VoIP</string>
<string name="notice_voip_started">بدأ اجتماع VoIP</string>
<string name="notice_voip_finished">انتهى اجتماع VoIP</string>
<string name="notice_room_name_removed">أزال %1$s اسم الغرفة</string>
<string name="notice_room_topic_removed">أزال %1$s موضوع الغرفة</string>
<string name="summary_user_sent_image">%1$s قد أرسل صورة.</string>
<string name="notice_room_invite_no_invitee">دعوة من %s</string>
<string name="notice_room_invite">%1$s قد دعى %2$s</string>
<string name="notice_room_invite_you">%1$s قد دعاك أنت</string>
<string name="notice_room_join">%1$s قد إنضّم إلى الغرفة</string>
<string name="notice_room_leave">%1$s قد غادر الغرفة</string>
<string name="notice_room_reject">%1$s قد رفض الدعوة</string>
<string name="notice_room_kick">%1$s قد طرد %2$s</string>
<string name="notice_room_unban">%1$s قد رفع الحظر عن %2$s</string>
<string name="notice_room_ban">%1$s قد حظر %2$s</string>
<string name="notice_avatar_url_changed">%1$s قد غيّر صورته الشخصية</string>
<string name="notice_display_name_set">%1$s قد عيّن اسمه الظاهر إلى %2$s</string>
<string name="notice_display_name_changed_from">%1$s قد غيّر اسمه الظاهر من %2$s إلى %3$s</string>
<string name="notice_display_name_removed">%1$s قد أزال اسمه الظاهر (لقد كان %2$s)</string>
<string name="notice_room_topic_changed">%1$s قد غيّر الموضوع إلى: %2$s</string>
<string name="notice_room_name_changed">%1$s قد غيّر اسم الغرفة إلى: %2$s</string>
<string name="notice_answered_call">%s قد أجاب على المُكالمة.</string>
<string name="notice_ended_call">%s قد أنهى المُكالمة.</string>
<string name="notice_made_future_room_visibility">%1$s قد جعل التأريخ المُستقبلي للغرفة مرئيًا لـ %2$s</string>
<string name="notice_room_visibility_invited">جميع أعضاء الغرفة، من اللحظة التي تمت دعوتهم.</string>
<string name="notice_room_visibility_joined">جميع أعضاء الغرفة، من لحظة انضمامهم.</string>
<string name="notice_room_visibility_shared">جميع أعضاء الغرفة.</string>
<string name="notice_room_visibility_world_readable">أيُّ شخص.</string>
<string name="notice_room_visibility_unknown">غير معروف (%s).</string>
<string name="notice_end_to_end">%1$s قد فعّل تعمية النهاية-إلى-النهاية (%2$s)</string>
<string name="notice_requested_voip_conference">%1$s قد طلب اجتماع VoIP</string>
<string name="notice_voip_started">اجتماع VoIP قد بدأ</string>
<string name="notice_voip_finished">اجتماع VoIP قد انتهى</string>
<string name="notice_room_name_removed">%1$s قد أزال اسم الغرفة</string>
<string name="notice_room_topic_removed">%1$s قد أزال موضوع الغرفة</string>
<string name="notice_profile_change_redacted">حدّث %1$s اللاحة %2$s</string>
<string name="notice_room_third_party_invite">أرسل %1$s دعوة إلى %2$s للانضمام إلى الغرفة</string>
<string name="notice_crypto_unable_to_decrypt">** تعذّر فك التعمية: %s **</string>
<string name="notice_crypto_error_unkwown_inbound_session_id">لم يُرسل جهاز المرسل مفاتيح هذه الرسالة.</string>
<string name="unable_to_send_message">تعذّر إرسال الرسالة</string>
<string name="message_failed_to_upload">فشل رفع الصورة</string>
<string name="network_error">خطأ في الشبكة</string>
<string name="matrix_error">خطأ في «ماترِكس»</string>
<string name="room_error_join_failed_empty_room">لا يمكنك حاليًا الانضمام ثانيةً إلى غرفة فارغة.</string>
<string name="encrypted_message">رسالة معمّاة</string>
<string name="medium_email">عنوان البريد الإلكتروني</string>
<string name="medium_phone_number">رقم الهاتف</string>
<string name="summary_message">%1$s: %2$s</string>
<string name="notice_room_withdraw">انسحب %1$s من دعوة %2$s</string>
<string name="notice_placed_video_call">أجرى %s مكالمة مرئية.</string>
<string name="notice_placed_voice_call">أجرى %s مكالمة صوتية.</string>
<string name="notice_room_withdraw">%1$s قد سحب دعوة %2$s</string>
<string name="notice_placed_video_call">%s قد أجرى مُكالمة مرئية.</string>
<string name="notice_placed_voice_call">%s قد أجرى مُكالمة صوتية.</string>
<string name="notice_room_third_party_registered_invite">قَبِل %1$s دعوة %2$s</string>
<string name="could_not_redact">تعذر التهذيب</string>
<string name="summary_user_sent_sticker">أرسل %1$s ملصقًا.</string>
<string name="notice_avatar_changed_too">(تغيّرت الصورة أيضا)</string>
<string name="summary_user_sent_sticker">%1$s قد أرسل مُلصقًا.</string>
<string name="notice_avatar_changed_too">(تمَّ تغيير الصورة أيضًا)</string>
<string name="room_displayname_invite_from">دعوة من %s</string>
<string name="room_displayname_empty_room">غرفة فارغة</string>
<string name="room_displayname_two_members">%1$s و %2$s</string>
<string name="room_displayname_room_invite">دعوة إلى غرفة</string>
<plurals name="room_displayname_three_and_more_members">
<item quantity="zero"></item>
<item quantity="one"></item>
<item quantity="two"></item>
<item quantity="few"></item>
<item quantity="many"></item>
<item quantity="other"></item>
<item quantity="zero"/>
<item quantity="one"/>
<item quantity="two"/>
<item quantity="few"/>
<item quantity="many"/>
<item quantity="other"/>
</plurals>
<string name="summary_you_sent_image">أرسلت صورة.</string>
<string name="summary_you_sent_sticker">أرسلت ملصقًا.</string>
<string name="summary_you_sent_image">أنت قد أرسلت صورة.</string>
<string name="summary_you_sent_sticker">أنت قد أرسلت مُلصقًا.</string>
<string name="notice_room_invite_no_invitee_by_you">دعوة منك أنت</string>
<string name="notice_room_created">أنشأ %1$s الغرفة</string>
<string name="notice_room_created_by_you">أنشأت الغرفة</string>
<string name="notice_room_invite_by_you">دعوت %1$s</string>
<string name="notice_room_join_by_you">انضممت إلى الغرفة</string>
<string name="notice_room_leave_by_you">غادرت الغرفة</string>
<string name="notice_room_reject_by_you">رفضت الدعوة</string>
<string name="notice_room_kick_by_you">طردت %1$s</string>
<string name="notice_room_unban_by_you">رفعت المنع عن %1$s</string>
<string name="notice_room_ban_by_you">منعت %1$s</string>
<string name="notice_room_withdraw_by_you">انسحبت من دعوة %1$s</string>
<string name="notice_avatar_url_changed_by_you">غيّرت صورتك</string>
<string name="notice_display_name_set_by_you">ضبطت اسم العرض على %1$s</string>
<string name="notice_display_name_changed_from_by_you">غيّرت اسم العرض من %1$s إلى %2$s</string>
<string name="notice_display_name_removed_by_you">أزلت اسم العرض (كان %1$s)</string>
<string name="notice_room_topic_changed_by_you">غيّرت الموضوع إلى: %1$s</string>
<string name="notice_room_avatar_changed">غيّر %1$s صورة الغرفة</string>
<string name="notice_room_avatar_changed_by_you">غيّرت صورة الغرفة</string>
<string name="notice_room_name_changed_by_you">غيّرت اسم الغرفة إلى: %1$s</string>
<string name="notice_placed_video_call_by_you">أجريت مكالمة مرئية.</string>
<string name="notice_placed_voice_call_by_you">أجريت مكالمة صوتية.</string>
<string name="notice_call_candidates">أرسل %s البيانات لإعداد المكالمة.</string>
<string name="notice_call_candidates_by_you">أرسلت البيانات لإعداد المكالمة.</string>
<string name="notice_answered_call_by_you">رددت على المكالمة.</string>
<string name="notice_ended_call_by_you">أنهيت المكالمة.</string>
<string name="notice_made_future_room_visibility_by_you">جعلت تأريخ الغرفة مستقبلًا ظاهرًا على %1$s</string>
<string name="notice_end_to_end_by_you">فعّلت تعمية الطرفين (%1$s)</string>
<string name="notice_room_update">رقّى %s هذه الغرفة.</string>
<string name="notice_room_update_by_you">رقّيت هذه الغرفة.</string>
<string name="notice_requested_voip_conference_by_you">طلبت اجتماع VoIP</string>
<string name="notice_room_name_removed_by_you">أزلت اسم الغرفة</string>
<string name="notice_room_topic_removed_by_you">أزلت موضوع الغرفة</string>
<string name="notice_room_avatar_removed">أزال %1$s صورة الغرفة</string>
<string name="notice_room_avatar_removed_by_you">أزلت صورة الغرفة</string>
<string name="notice_event_redacted">أُزيلت الرسالة</string>
<string name="notice_event_redacted_by">أزال %1$s الرسالة</string>
<string name="notice_room_created">%1$s قد أنشأ الغرفة</string>
<string name="notice_room_created_by_you">أنت قد أنشأت الغرفة</string>
<string name="notice_room_invite_by_you">أنت قد دعوت %1$s</string>
<string name="notice_room_join_by_you">أنت قد انضممت إلى الغرفة</string>
<string name="notice_room_leave_by_you">أنت قد غادرت الغرفة</string>
<string name="notice_room_reject_by_you">أنت قد رفضت الدعوة</string>
<string name="notice_room_kick_by_you">أنت قد طردت %1$s</string>
<string name="notice_room_unban_by_you">أنت قد رفعت الحظر عن %1$s</string>
<string name="notice_room_ban_by_you">أنت قد حظرت %1$s</string>
<string name="notice_room_withdraw_by_you">أنت قد سحبت دعوة %1$s</string>
<string name="notice_avatar_url_changed_by_you">أنت قد غيّرت صورتك الشخصية</string>
<string name="notice_display_name_set_by_you">أنت قد عيّنت اسمك الظاهر إلى %1$s</string>
<string name="notice_display_name_changed_from_by_you">أنت قد غيّرت اسمك الظاهر من %1$s إلى %2$s</string>
<string name="notice_display_name_removed_by_you">أنت قد أزلت اسمك الظاهر (لقد كان %1$s)</string>
<string name="notice_room_topic_changed_by_you">أنت قد غيّرت الموضوع إلى: %1$s</string>
<string name="notice_room_avatar_changed">%1$s قد غيّر صورة الغرفة</string>
<string name="notice_room_avatar_changed_by_you">أنت قد غيّرت صورة الغرفة</string>
<string name="notice_room_name_changed_by_you">أنت قد غيّرت اسم الغرفة إلى: %1$s</string>
<string name="notice_placed_video_call_by_you">أنت قد أجريت مُكالمة مرئية.</string>
<string name="notice_placed_voice_call_by_you">أنت قد أجريت مُكالمة صوتية.</string>
<string name="notice_call_candidates">%s قد أرسل بيانات لإعداد مُكالمة.</string>
<string name="notice_call_candidates_by_you">أنت قد أرسلت بيانات لإعداد مُكالمة.</string>
<string name="notice_answered_call_by_you">أنت قد أجبت على المُكالمة.</string>
<string name="notice_ended_call_by_you">أنت قد أنهيت المُكالمة.</string>
<string name="notice_made_future_room_visibility_by_you">أنت قد جعلت التأريخ المُستقبلي للغرفة مرئيًا لـ %1$s</string>
<string name="notice_end_to_end_by_you">أنت قد فعّلت تعيمية النهاية-إلى-النهاية (%1$s)</string>
<string name="notice_room_update">%s قد قام بترقية هذه الغرفة.</string>
<string name="notice_room_update_by_you">أنت قد رقّيتَ هذه الغرفة.</string>
<string name="notice_requested_voip_conference_by_you">أنت قد طلبت اجتماع VoIP</string>
<string name="notice_room_name_removed_by_you">أنت قد أزلت اسم الغرفة</string>
<string name="notice_room_topic_removed_by_you">أنت قد أزلت موضوع الغرفة</string>
<string name="notice_room_avatar_removed">%1$s قد أزال صورة الغرفة</string>
<string name="notice_room_avatar_removed_by_you">أنت قد أزلت صورة الغرفة</string>
<string name="notice_event_redacted">تمت إزالة الرسالة</string>
<string name="notice_event_redacted_by">الرسالة قد أُزيلت بواسطة %1$s</string>
<string name="notice_event_redacted_with_reason">أُزيلت الرسالة [السبب: %1$s]</string>
<string name="notice_event_redacted_by_with_reason">أزال %1$s الرسالة [السبب: %2$s]</string>
<string name="notice_room_third_party_invite_by_you">أرسلت دعوة إلى %1$s للانضمام إلى الغرفة</string>
<string name="notice_room_third_party_revoked_invite">سحب %1$s دعوة %2$s للانضمام إلى الغرفة</string>
<string name="notice_room_third_party_revoked_invite_by_you">سحبت دعوة %1$s للانضمام إلى الغرفة</string>
<string name="notice_room_third_party_registered_invite_by_you">قَبِلت دعوة %1$s</string>
<string name="notice_widget_added">أضاف %1$s الودجة %2$s</string>
<string name="notice_widget_added_by_you">أضفت الودجة %1$s</string>
<string name="notice_widget_removed">أزال %1$s الودجة %2$s</string>
<string name="notice_widget_removed_by_you">أزلت الودجة %1$s</string>
<string name="notice_widget_modified">عدّل %1$s الودجة %2$s</string>
<string name="notice_widget_modified_by_you">عدّلت الودجة %1$s</string>
<string name="power_level_admin">مدير</string>
<string name="power_level_default">المبدئي</string>
<string name="power_level_custom">مخصّص (%1$d)</string>
<string name="power_level_custom_no_value">مخصّص</string>
<string name="notice_power_level_changed_by_you">غيّرت مستوى قوّة %1$s.</string>
<string name="notice_power_level_changed">غيّر %1$s مستوى قوّة %2$s.</string>
<string name="notice_power_level_diff">%1$s من %2$s إلى %3$s</string>
<string name="initial_sync_start_importing_account">المزامنة الأولية:
\nيستورد الحساب…</string>
<string name="notice_room_server_acl_allow_is_empty">🎉 جميع الخوادم محظورة من المُشاركة! لم يعُد من الممكن استخدام هذه الغرفة.</string>
<string name="notice_room_server_acl_updated_no_change">لا تغيير.</string>
<string name="notice_room_server_acl_updated_ip_literals_not_allowed">• خوادم مُطابقة IP الحرفية محظورة الآن.</string>
<string name="notice_room_server_acl_updated_was_allowed">• الخادم المُطابق لـ %s قد أُزيل من قائمة السماح.</string>
<string name="notice_room_server_acl_updated_allowed">• الخادم المُطابق لـ %s مسموح الآن.</string>
<string name="notice_room_server_acl_updated_was_banned">• الخادم المُطابق لـ %s قد أُزيل من قائمة الحظر.</string>
<string name="notice_room_server_acl_updated_banned">• الخادم المُطابق لـ %s محظور الآن.</string>
<string name="notice_room_server_acl_updated_ip_literals_allowed">• خوادم مُطابقة IP الحرفية مسموحة الآن.</string>
<string name="notice_room_server_acl_updated_title_by_you">أنت قد غيّرت خادم الـACLs لهذه الغرفة.</string>
<string name="notice_room_server_acl_updated_title">%s قد غيّر خادم الـACLs لهذه الغرفة.</string>
<string name="notice_room_server_acl_set_ip_literals_not_allowed">• الخادم يحظر مُطابقة القيم الحرفية للـIP.</string>
<string name="notice_room_server_acl_set_allowed">• الخادم المُطابق لـ %s مسموح.</string>
<string name="notice_room_server_acl_set_banned">• الخادم المُطابق لـ %s محظور.</string>
<string name="notice_room_server_acl_set_ip_literals_allowed">• الخادم يسمح بمُطابقة القيم الحرفية للـIP.</string>
<string name="notice_room_server_acl_set_title_by_you">أنت قد عيّنت خادم الـACLs لهذه الغرفة.</string>
<string name="notice_room_server_acl_set_title">%s قد عيّن خادم الـACLs لهذه الغرفة.</string>
<string name="notice_direct_room_update_by_you">أنت قد قمت بالترقية هُنا.</string>
<string name="notice_direct_room_update">%s قد قام بالترقية هُنا.</string>
<string name="notice_made_future_direct_room_visibility_by_you">أنت قد جعلت الرسائل المُستقبلية مرئية لـ %1$s</string>
<string name="notice_made_future_direct_room_visibility">%1$s قد جعل الرسائل المُستقبلية مرئية لـ %2$s</string>
<string name="notice_direct_room_leave_by_you">أنت قد غادرت الغرفة</string>
<string name="notice_direct_room_leave">%1$s قد غادر الغرفة</string>
<string name="notice_direct_room_join_by_you">أنت قد انضممت</string>
<string name="notice_direct_room_join">%1$s قد انضم</string>
<string name="notice_direct_room_created_by_you">أنت قد أنشأت المُناقشة</string>
<string name="notice_direct_room_created">%1$s قد أنشأ المُناقشة</string>
</resources>

View file

@ -259,4 +259,10 @@
<string name="notice_room_server_acl_updated_title">%s ha canviat les ACLs de servidor d\'aquesta sala.</string>
<string name="notice_room_server_acl_set_title_by_you">Has establert les ACLs de servidor per aquesta sala.</string>
<string name="notice_room_server_acl_set_title">%s ha establert les ACLs de servidor d\'aquesta sala.</string>
<string name="notice_widget_jitsi_modified_by_you">Has modificat la videoconferència</string>
<string name="notice_widget_jitsi_modified">%1$s ha modificat la videoconferència</string>
<string name="notice_widget_jitsi_removed_by_you">Has finalitzat la videoconferència</string>
<string name="notice_widget_jitsi_added">%1$s ha iniciat una videoconferència</string>
<string name="notice_widget_jitsi_added_by_you">Has iniciat una videoconferència</string>
<string name="notice_widget_jitsi_removed">%1$s ha finalitzat la videoconferència</string>
</resources>

View file

@ -266,4 +266,10 @@
<string name="notice_room_server_acl_set_banned">• Server, die mit %s übereinstimmen, sind gesperrt.</string>
<string name="notice_room_server_acl_set_title_by_you">Du hast die Server-ACL für diesen Raum gesetzt.</string>
<string name="notice_room_server_acl_set_title">%s hat die Server-Zugriffssteuerungsliste (ACL) für diesen Raum gesetzt.</string>
<string name="notice_widget_jitsi_modified_by_you">Du hast eine Videokonferenz geändert</string>
<string name="notice_widget_jitsi_modified">Videokonferenz von %1$s geändert</string>
<string name="notice_widget_jitsi_removed">Videokonferenz von %1$s beendet</string>
<string name="notice_widget_jitsi_removed_by_you">Du hast eine Videokonferenz beendet</string>
<string name="notice_widget_jitsi_added_by_you">Du hast eine Videokonferenz gestartet</string>
<string name="notice_widget_jitsi_added">Videokonferenz von %1$s gestartet</string>
</resources>

View file

@ -258,4 +258,10 @@
<item quantity="other">%1$s lisas sellele jututoale täiendavad aadressid %2$s.</item>
</plurals>
<string name="notice_room_canonical_alias_no_change_by_you">Sa muutsid selle jututoa aadresse.</string>
<string name="notice_widget_jitsi_modified_by_you">Sina muutsid videokoosolekut</string>
<string name="notice_widget_jitsi_removed_by_you">Sina lõpetasid videokoosoleku</string>
<string name="notice_widget_jitsi_removed">%1$s lõpetas videokoosoleku</string>
<string name="notice_widget_jitsi_added_by_you">Sina algatasid videokoosoleku</string>
<string name="notice_widget_jitsi_added">%1$s algatas videokoosoleku</string>
<string name="notice_widget_jitsi_modified">%1$s muutis videokoosolekut</string>
</resources>

View file

@ -259,4 +259,10 @@
<item quantity="one">%1$s ha aggiunto l\'indirizzo alternativo %2$s per questa stanza.</item>
<item quantity="other">%1$s ha aggiunto gli indirizzi alternativi %2$s per questa stanza.</item>
</plurals>
<string name="notice_widget_jitsi_modified_by_you">Hai modificato la video conferenza</string>
<string name="notice_widget_jitsi_modified">Video conferenza modificata da %1$s</string>
<string name="notice_widget_jitsi_added_by_you">Hai iniziato la video conferenza</string>
<string name="notice_widget_jitsi_removed_by_you">Hai terminato la video conferenza</string>
<string name="notice_widget_jitsi_removed">Video conferenza terminata da %1$s</string>
<string name="notice_widget_jitsi_added">Video conferenza iniziata da %1$s</string>
</resources>

View file

@ -268,4 +268,10 @@
<item quantity="few">%1$s уклони %2$s као адресе ове собе.</item>
<item quantity="other">%1$s уклони %2$s као адресе ове собе.</item>
</plurals>
<string name="notice_widget_jitsi_modified_by_you">Изменили сте видео конференцију</string>
<string name="notice_widget_jitsi_modified">%1$s измени видео конференцију</string>
<string name="notice_widget_jitsi_removed_by_you">Завршили сте видео конференцију</string>
<string name="notice_widget_jitsi_removed">%1$s заврши видео конференцију</string>
<string name="notice_widget_jitsi_added_by_you">Покренули сте видео конференцију</string>
<string name="notice_widget_jitsi_added">%1$s покрену видео конференцију</string>
</resources>

View file

@ -17,6 +17,10 @@
<issue id="ButtonOrder" severity="error" />
<issue id="TextFields" severity="error" />
<!-- Accessibility -->
<issue id="LabelFor" severity="error" />
<issue id="ContentDescription" severity="error" />
<!-- Layout -->
<issue id="UnknownIdInLayout" severity="error" />
<issue id="StringFormatCount" severity="error" />

View file

@ -159,6 +159,7 @@
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:src="@drawable/ic_settings_x" />
</LinearLayout>

View file

@ -267,6 +267,7 @@
<!-- </intent-filter>-->
</activity>
<activity android:name=".features.devtools.RoomDevToolActivity"/>
<!-- Services -->
<service

View file

@ -21,6 +21,7 @@
div {
padding: 4px;
}
</style>
</head>
<body>
@ -388,7 +389,7 @@ SOFTWARE.
<li>
<b>dialogs / android-dialer</b>
<br/>
Copyright (c) 2017-present, dialog LLC <info@dlg.im>
Copyright (c) 2017-present, dialog LLC &lt;info@dlg.im&gt;
</li>
</ul>
<pre>
@ -570,20 +571,24 @@ Apache License
<pre>
CC-BY 4.0
</pre>
<ul>
<li>
<b>Twitter/twemoji Graphics</b>
<br/>
</li>
</pre>
</ul>
<pre>
ISC License
</pre>
<ul>
<li>
<b>DanielMartinus / Konfetti</b>
<br/>
Copyright (c) 2017 Dion Segijn
</li>
</pre>
</ul>
</body>
</html>

View file

@ -23,7 +23,6 @@ import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
import im.vector.app.features.notifications.PushRuleTriggerListener
import im.vector.app.features.session.SessionListener
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.session.Session
import timber.log.Timber
import java.util.concurrent.atomic.AtomicReference
@ -31,8 +30,7 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ActiveSessionHolder @Inject constructor(private val authenticationService: AuthenticationService,
private val sessionObservableStore: ActiveSessionDataSource,
class ActiveSessionHolder @Inject constructor(private val sessionObservableStore: ActiveSessionDataSource,
private val keyRequestHandler: KeyRequestHandler,
private val incomingVerificationRequestHandler: IncomingVerificationRequestHandler,
private val callManager: WebRtcCallManager,

View file

@ -45,6 +45,10 @@ import im.vector.app.features.crypto.verification.emoji.VerificationEmojiCodeFra
import im.vector.app.features.crypto.verification.qrconfirmation.VerificationQRWaitingFragment
import im.vector.app.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment
import im.vector.app.features.crypto.verification.request.VerificationRequestFragment
import im.vector.app.features.devtools.RoomDevToolEditFragment
import im.vector.app.features.devtools.RoomDevToolFragment
import im.vector.app.features.devtools.RoomDevToolSendFormFragment
import im.vector.app.features.devtools.RoomDevToolStateEventListFragment
import im.vector.app.features.discovery.DiscoverySettingsFragment
import im.vector.app.features.discovery.change.SetIdentityServerFragment
import im.vector.app.features.grouplist.GroupListFragment
@ -594,4 +598,24 @@ interface FragmentModule {
@IntoMap
@FragmentKey(ShowUserCodeFragment::class)
fun bindShowUserCodeFragment(fragment: ShowUserCodeFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomDevToolFragment::class)
fun bindRoomDevToolFragment(fragment: RoomDevToolFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomDevToolStateEventListFragment::class)
fun bindRoomDevToolStateEventListFragment(fragment: RoomDevToolStateEventListFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomDevToolEditFragment::class)
fun bindRoomDevToolEditFragment(fragment: RoomDevToolEditFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomDevToolSendFormFragment::class)
fun bindRoomDevToolSendFormFragment(fragment: RoomDevToolSendFormFragment): Fragment
}

View file

@ -36,6 +36,7 @@ import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
import im.vector.app.features.crypto.recover.BootstrapBottomSheet
import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.debug.DebugMenuActivity
import im.vector.app.features.devtools.RoomDevToolActivity
import im.vector.app.features.home.HomeActivity
import im.vector.app.features.home.HomeModule
import im.vector.app.features.home.room.detail.RoomDetailActivity
@ -149,6 +150,7 @@ interface ScreenComponent {
fun inject(activity: UserCodeActivity)
fun inject(activity: CallTransferActivity)
fun inject(activity: ReAuthActivity)
fun inject(activity: RoomDevToolActivity)
/* ==========================================================================================
* BottomSheets

View file

@ -61,7 +61,7 @@ class ExportKeysDialog {
passwordVisible = !passwordVisible
views.exportDialogEt.showPassword(passwordVisible)
views.exportDialogEtConfirm.showPassword(passwordVisible)
views.exportDialogShowPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.exportDialogShowPassword.render(passwordVisible)
}
val exportDialog = builder.show()

View file

@ -44,7 +44,7 @@ class PromptPasswordDialog {
views.promptPasswordPasswordReveal.setOnClickListener {
passwordVisible = !passwordVisible
views.promptPassword.showPassword(passwordVisible)
views.promptPasswordPasswordReveal.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.promptPasswordPasswordReveal.render(passwordVisible)
}
AlertDialog.Builder(activity)

View file

@ -21,7 +21,6 @@ import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.features.crypto.util.toImageRes
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.util.MatrixItem
@ -47,6 +46,6 @@ abstract class BaseProfileMatrixItem<T : ProfileMatrixItem.Holder> : VectorEpoxy
holder.subtitleView.setTextOrHide(matrixId)
holder.editableView.isVisible = editable
avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.avatarDecorationImageView.setImageResource(userEncryptionTrustLevel.toImageRes())
holder.avatarDecorationImageView.render(userEncryptionTrustLevel)
}
}

View file

@ -23,6 +23,7 @@ import android.widget.TextView
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.ui.views.ShieldImageView
@EpoxyModelClass(layout = R.layout.item_profile_matrix_item)
abstract class ProfileMatrixItem : BaseProfileMatrixItem<ProfileMatrixItem.Holder>() {
@ -31,7 +32,7 @@ abstract class ProfileMatrixItem : BaseProfileMatrixItem<ProfileMatrixItem.Holde
val titleView by bind<TextView>(R.id.matrixItemTitle)
val subtitleView by bind<TextView>(R.id.matrixItemSubtitle)
val avatarImageView by bind<ImageView>(R.id.matrixItemAvatar)
val avatarDecorationImageView by bind<ImageView>(R.id.matrixItemAvatarDecoration)
val avatarDecorationImageView by bind<ShieldImageView>(R.id.matrixItemAvatarDecoration)
val editableView by bind<View>(R.id.matrixItemEditable)
}
}

View file

@ -19,17 +19,16 @@ package im.vector.app.core.extensions
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import im.vector.app.core.utils.EventObserver
import im.vector.app.core.utils.FirstThrottler
import im.vector.app.core.utils.LiveEvent
inline fun <T> LiveData<T>.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) {
this.observe(owner, Observer { observer(it) })
this.observe(owner, { observer(it) })
}
inline fun <T> LiveData<T>.observeNotNull(owner: LifecycleOwner, crossinline observer: (T) -> Unit) {
this.observe(owner, Observer { it?.run(observer) })
this.observe(owner, { it?.run(observer) })
}
inline fun <T> LiveData<LiveEvent<T>>.observeEvent(owner: LifecycleOwner, crossinline observer: (T) -> Unit) {

View file

@ -40,7 +40,6 @@ import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentFactory
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
import com.bumptech.glide.util.Util
@ -208,12 +207,12 @@ abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScr
navigator = screenComponent.navigator()
activeSessionHolder = screenComponent.activeSessionHolder()
vectorPreferences = vectorComponent.vectorPreferences()
configurationViewModel.activityRestarter.observe(this, Observer {
configurationViewModel.activityRestarter.observe(this) {
if (!it.hasBeenHandled) {
// Recreate the Activity because configuration has changed
restart()
}
})
}
pinLocker.getLiveState().observeNotNull(this) {
if (this@VectorBaseActivity !is UnlockedActivity && it == PinLocker.State.LOCKED) {
navigator.openPinCode(this, pinStartForActivityResult, PinMode.AUTH)

View file

@ -48,7 +48,7 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
}
@EpoxyAttribute
var title: String? = null
var title: CharSequence? = null
@EpoxyAttribute
var description: CharSequence? = null

View file

@ -89,7 +89,7 @@ class KnownCallsViewHolder {
this.pipWrapper = pipWrapper
this.currentCallsView?.callback = interactionListener
pipWrapper.setOnClickListener(
DebouncedClickListener({ _ ->
DebouncedClickListener({
interactionListener.onTapToReturnToCall()
})
)

View file

@ -56,6 +56,7 @@ class ReadReceiptsView @JvmOverloads constructor(
private fun setupView() {
inflate(context, R.layout.view_read_receipts, this)
contentDescription = context.getString(R.string.a11y_view_read_receipts)
}
fun render(readReceipts: List<ReadReceiptData>, avatarRenderer: AvatarRenderer, clickListener: OnClickListener) {

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.ui.views
import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import im.vector.app.R
class RevealPasswordImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
init {
render(false)
}
fun render(isPasswordShown: Boolean) {
if (isPasswordShown) {
contentDescription = context.getString(R.string.a11y_hide_password)
setImageResource(R.drawable.ic_eye_closed)
} else {
contentDescription = context.getString(R.string.a11y_show_password)
setImageResource(R.drawable.ic_eye)
}
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.ui.views
import android.content.Context
import android.util.AttributeSet
import androidx.annotation.DrawableRes
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.view.isVisible
import im.vector.app.R
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
class ShieldImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
init {
if (isInEditMode) {
render(RoomEncryptionTrustLevel.Trusted)
}
}
fun render(roomEncryptionTrustLevel: RoomEncryptionTrustLevel?) {
isVisible = roomEncryptionTrustLevel != null
when (roomEncryptionTrustLevel) {
RoomEncryptionTrustLevel.Default -> {
contentDescription = context.getString(R.string.a11y_trust_level_default)
setImageResource(R.drawable.ic_shield_black)
}
RoomEncryptionTrustLevel.Warning -> {
contentDescription = context.getString(R.string.a11y_trust_level_warning)
setImageResource(R.drawable.ic_shield_warning)
}
RoomEncryptionTrustLevel.Trusted -> {
contentDescription = context.getString(R.string.a11y_trust_level_trusted)
setImageResource(R.drawable.ic_shield_trusted)
}
}
}
}
@DrawableRes
fun RoomEncryptionTrustLevel.toDrawableRes(): Int {
return when (this) {
RoomEncryptionTrustLevel.Default -> R.drawable.ic_shield_black
RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning
RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted
}
}

View file

@ -27,8 +27,8 @@ class CountUpTimer(private val intervalInMs: Long) {
private val resumed: AtomicBoolean = AtomicBoolean(false)
private val disposable = Observable.interval(intervalInMs, TimeUnit.MILLISECONDS)
.filter { _ -> resumed.get() }
.doOnNext { _ -> elapsedTime.addAndGet(intervalInMs) }
.filter { resumed.get() }
.doOnNext { elapsedTime.addAndGet(intervalInMs) }
.subscribe {
tickListener?.onTick(elapsedTime.get())
}

View file

@ -49,7 +49,7 @@ open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableD
private fun createRelay(): BehaviorRelay<T> {
return if (defaultValue == null) {
BehaviorRelay.create<T>()
BehaviorRelay.create()
} else {
BehaviorRelay.createDefault(defaultValue)
}

View file

@ -19,12 +19,11 @@ package im.vector.app.core.utils
import io.reactivex.Completable
import io.reactivex.Single
import io.reactivex.disposables.Disposable
import io.reactivex.functions.Consumer
import io.reactivex.internal.functions.Functions
import timber.log.Timber
fun <T> Single<T>.subscribeLogError(): Disposable {
return subscribe(Functions.emptyConsumer(), Consumer { Timber.e(it) })
return subscribe(Functions.emptyConsumer(), { Timber.e(it) })
}
fun Completable.subscribeLogError(): Disposable {

View file

@ -87,14 +87,7 @@ class PromptFragment : VectorBaseFragment<FragmentReauthConfirmBinding>() {
}
views.passwordField.showPassword(it.passwordVisible)
if (it.passwordVisible) {
views.passwordReveal.setImageResource(R.drawable.ic_eye_closed)
views.passwordReveal.contentDescription = getString(R.string.a11y_hide_password)
} else {
views.passwordReveal.setImageResource(R.drawable.ic_eye)
views.passwordReveal.contentDescription = getString(R.string.a11y_show_password)
}
views.passwordReveal.render(it.passwordVisible)
if (it.lastErrorCode != null) {
when (it.flowType) {

View file

@ -102,7 +102,7 @@ abstract class RecyclerViewPresenter<T>(context: Context?) : AutocompletePresent
return LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
}
private class Observer internal constructor(private val root: DataSetObserver) : RecyclerView.AdapterDataObserver() {
private class Observer constructor(private val root: DataSetObserver) : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
root.onChanged()
}

View file

@ -31,7 +31,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
@Assisted val roomId: String,
private val session: Session,
session: Session,
private val controller: AutocompleteMemberController
) : RecyclerViewPresenter<RoomMemberSummary>(context), AutocompleteClickListener<RoomMemberSummary> {

View file

@ -383,7 +383,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
mode: String?): Intent {
return Intent(context, VectorCallActivity::class.java).apply {
// what could be the best flags?
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
flags = FLAG_ACTIVITY_CLEAR_TOP
putExtra(MvRx.KEY_ARG, CallArgs(roomId, callId, otherUserId, isIncomingCall, isVideoCall))
putExtra(EXTRA_MODE, mode)
}

View file

@ -19,7 +19,6 @@ import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.Observer
import im.vector.app.R
import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.observeEvent
@ -54,7 +53,7 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
viewModel = viewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)
viewModel.initSession(session)
viewModel.keySourceModel.observe(this, Observer { keySource ->
viewModel.keySourceModel.observe(this) { keySource ->
if (keySource != null && !keySource.isInQuadS && supportFragmentManager.fragments.isEmpty()) {
val isBackupCreatedFromPassphrase =
viewModel.keyVersionResult.value?.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null
@ -64,7 +63,7 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
replaceFragment(R.id.container, KeysBackupRestoreFromKeyFragment::class.java)
}
}
})
}
viewModel.keyVersionResultError.observeEvent(this) { message ->
AlertDialog.Builder(this)
@ -111,9 +110,9 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
}
}
viewModel.loadingEvent.observe(this, Observer {
viewModel.loadingEvent.observe(this) {
updateWaitingView(it)
})
}
viewModel.importRoomKeysFinishWithResult.observeEvent(this) {
// set data?

View file

@ -22,7 +22,6 @@ import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.Observer
import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
@ -56,9 +55,9 @@ class KeysBackupRestoreFromKeyFragment @Inject constructor()
}
views.keyInputLayout.error = viewModel.recoveryCodeErrorText.value
viewModel.recoveryCodeErrorText.observe(viewLifecycleOwner, Observer { newValue ->
viewModel.recoveryCodeErrorText.observe(viewLifecycleOwner) { newValue ->
views.keyInputLayout.error = newValue
})
}
views.keysRestoreButton.setOnClickListener { onRestoreFromKey() }
views.keysBackupImport.setOnClickListener { onImport() }

View file

@ -24,7 +24,6 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.core.text.set
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.Observer
import im.vector.app.R
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment
@ -51,17 +50,17 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBase
viewModel = fragmentViewModelProvider.get(KeysBackupRestoreFromPassphraseViewModel::class.java)
sharedViewModel = activityViewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)
viewModel.passphraseErrorText.observe(viewLifecycleOwner, Observer { newValue ->
viewModel.passphraseErrorText.observe(viewLifecycleOwner) { newValue ->
views.keysBackupPassphraseEnterTil.error = newValue
})
}
views.helperTextWithLink.text = spannableStringForHelperText()
viewModel.showPasswordMode.observe(viewLifecycleOwner, Observer {
viewModel.showPasswordMode.observe(viewLifecycleOwner) {
val shouldBeVisible = it ?: false
views.keysBackupPassphraseEnterEdittext.showPassword(shouldBeVisible)
views.keysBackupViewShowPassword.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
})
views.keysBackupViewShowPassword.render(shouldBeVisible)
}
views.keysBackupPassphraseEnterEdittext.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {

View file

@ -21,7 +21,6 @@ import android.content.Intent
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer
import im.vector.app.R
import im.vector.app.core.dialogs.ExportKeysDialog
import im.vector.app.core.extensions.observeEvent
@ -49,20 +48,20 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
viewModel.showManualExport.value = intent.getBooleanExtra(EXTRA_SHOW_MANUAL_EXPORT, false)
viewModel.initSession(session)
viewModel.isCreatingBackupVersion.observe(this, Observer {
viewModel.isCreatingBackupVersion.observe(this) {
val isCreating = it ?: false
if (isCreating) {
showWaitingView()
} else {
hideWaitingView()
}
})
}
viewModel.loadingStatus.observe(this, Observer {
viewModel.loadingStatus.observe(this) {
it?.let {
updateWaitingView(it)
}
})
}
viewModel.navigateEvent.observeEvent(this) { uxStateEvent ->
when (uxStateEvent) {
@ -99,7 +98,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
}
}
viewModel.prepareRecoverFailError.observe(this, Observer { error ->
viewModel.prepareRecoverFailError.observe(this) { error ->
if (error != null) {
AlertDialog.Builder(this)
.setTitle(R.string.unknown_error)
@ -110,9 +109,9 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
}
.show()
}
})
}
viewModel.creatingBackupError.observe(this, Observer { error ->
viewModel.creatingBackupError.observe(this) { error ->
if (error != null) {
AlertDialog.Builder(this)
.setTitle(R.string.unexpected_error)
@ -123,7 +122,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
}
.show()
}
})
}
}
private val saveStartForActivityResult = registerStartForActivityResult { activityResult ->

View file

@ -20,7 +20,6 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.LiveEvent
import im.vector.app.databinding.FragmentKeysBackupSetupStep1Binding
@ -40,12 +39,12 @@ class KeysBackupSetupStep1Fragment @Inject constructor() : VectorBaseFragment<Fr
viewModel = activityViewModelProvider.get(KeysBackupSetupSharedViewModel::class.java)
viewModel.showManualExport.observe(viewLifecycleOwner, Observer {
viewModel.showManualExport.observe(viewLifecycleOwner) {
val showOption = it ?: false
// Can't use isVisible because the kotlin compiler will crash with Back-end (JVM) Internal error: wrong code generated
views.keysBackupSetupStep1AdvancedOptionText.visibility = if (showOption) View.VISIBLE else View.GONE
views.keysBackupSetupStep1ManualExportButton.visibility = if (showOption) View.VISIBLE else View.GONE
})
}
views.keysBackupSetupStep1Button.setOnClickListener { onButtonClick() }
views.keysBackupSetupStep1ManualExportButton.setOnClickListener { onManualExportClick() }

View file

@ -21,7 +21,6 @@ import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.Observer
import androidx.lifecycle.viewModelScope
import androidx.transition.TransitionManager
import com.nulabinc.zxcvbn.Zxcvbn
@ -30,7 +29,6 @@ import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentKeysBackupSetupStep2Binding
import im.vector.app.features.settings.VectorLocale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -69,7 +67,7 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
* ========================================================================================== */
private fun bindViewToViewModel() {
viewModel.passwordStrength.observe(viewLifecycleOwner, Observer { strength ->
viewModel.passwordStrength.observe(viewLifecycleOwner) { strength ->
if (strength == null) {
views.keysBackupSetupStep2PassphraseStrengthLevel.strength = 0
views.keysBackupSetupStep2PassphraseEnterTil.error = null
@ -91,9 +89,9 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
views.keysBackupSetupStep2PassphraseEnterTil.error = null
}
}
})
}
viewModel.passphrase.observe(viewLifecycleOwner, Observer<String> { newValue ->
viewModel.passphrase.observe(viewLifecycleOwner) { newValue ->
if (newValue.isEmpty()) {
viewModel.passwordStrength.value = null
} else {
@ -104,28 +102,28 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
}
}
}
})
}
views.keysBackupSetupStep2PassphraseEnterEdittext.setText(viewModel.passphrase.value)
viewModel.passphraseError.observe(viewLifecycleOwner, Observer {
viewModel.passphraseError.observe(viewLifecycleOwner) {
TransitionManager.beginDelayedTransition(views.keysBackupRoot)
views.keysBackupSetupStep2PassphraseEnterTil.error = it
})
}
views.keysBackupSetupStep2PassphraseConfirmEditText.setText(viewModel.confirmPassphrase.value)
viewModel.showPasswordMode.observe(viewLifecycleOwner, Observer {
viewModel.showPasswordMode.observe(viewLifecycleOwner) {
val shouldBeVisible = it ?: false
views.keysBackupSetupStep2PassphraseEnterEdittext.showPassword(shouldBeVisible)
views.keysBackupSetupStep2PassphraseConfirmEditText.showPassword(shouldBeVisible)
views.keysBackupSetupStep2ShowPassword.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
})
views.keysBackupSetupStep2ShowPassword.render(shouldBeVisible)
}
viewModel.confirmPassphraseError.observe(viewLifecycleOwner, Observer {
viewModel.confirmPassphraseError.observe(viewLifecycleOwner) {
TransitionManager.beginDelayedTransition(views.keysBackupRoot)
views.keysBackupSetupStep2PassphraseConfirmTil.error = it
})
}
views.keysBackupSetupStep2PassphraseConfirmEditText.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {

View file

@ -25,7 +25,6 @@ import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import arrow.core.Try
import com.google.android.material.bottomsheet.BottomSheetDialog
import im.vector.app.R
@ -61,7 +60,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
viewModel.shouldPromptOnBack = false
viewModel.passphrase.observe(viewLifecycleOwner, Observer {
viewModel.passphrase.observe(viewLifecycleOwner) {
if (it.isNullOrBlank()) {
// Recovery was generated, so show key and options to save
views.keysBackupSetupStep3Label2.text = getString(R.string.keys_backup_setup_step3_text_line2_no_passphrase)
@ -81,7 +80,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
views.keysBackupSetupStep3FinishButton.text = getString(R.string.keys_backup_setup_step3_button_title)
views.keysBackupSetupStep3RecoveryKeyText.isVisible = false
}
})
}
setupViews()
}

View file

@ -183,11 +183,11 @@ class KeyRequestHandler @Inject constructor(
denyAllRequests(mappingKey)
}
alert.addButton(context.getString(R.string.share_without_verifying_short_label), Runnable {
alert.addButton(context.getString(R.string.share_without_verifying_short_label), {
shareAllSessions(mappingKey)
})
alert.addButton(context.getString(R.string.ignore_request_short_label), Runnable {
alert.addButton(context.getString(R.string.ignore_request_short_label), {
denyAllRequests(mappingKey)
})

View file

@ -106,6 +106,6 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
override fun invalidate() = withState(sharedViewModel) { state ->
val shouldBeVisible = state.passphraseVisible
views.ssssPassphraseEnterEdittext.showPassword(shouldBeVisible)
views.ssssViewShowPassword.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.ssssViewShowPassword.render(shouldBeVisible)
}
}

View file

@ -97,7 +97,7 @@ class BackupToQuadSMigrationTask @Inject constructor(
when {
params.passphrase?.isNotEmpty() == true -> {
reportProgress(params, R.string.bootstrap_progress_generating_ssss)
awaitCallback<SsssKeyCreationInfo> {
awaitCallback {
quadS.generateKeyWithPassphrase(
UUID.randomUUID().toString(),
"ssss_key",

View file

@ -109,7 +109,7 @@ class BootstrapConfirmPassphraseFragment @Inject constructor()
if (state.step is BootstrapStep.ConfirmPassphrase) {
val isPasswordVisible = state.step.isPasswordVisible
views.ssssPassphraseEnterEdittext.showPassword(isPasswordVisible, updateCursor = false)
views.ssssViewShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.ssssViewShowPassword.render(isPasswordVisible)
}
}
}

View file

@ -103,7 +103,7 @@ class BootstrapEnterPassphraseFragment @Inject constructor()
if (state.step is BootstrapStep.SetupPassphrase) {
val isPasswordVisible = state.step.isPasswordVisible
views.ssssPassphraseEnterEdittext.showPassword(isPasswordVisible, updateCursor = false)
views.ssssViewShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.ssssViewShowPassword.render(isPasswordVisible)
state.passphraseStrength.invoke()?.let { strength ->
val score = strength.score

View file

@ -133,7 +133,7 @@ class BootstrapMigrateBackupFragment @Inject constructor(
if (state.step is BootstrapStep.GetBackupSecretPassForMigration) {
val isPasswordVisible = state.step.isPasswordVisible
views.bootstrapMigrateEditText.showPassword(isPasswordVisible, updateCursor = false)
views.bootstrapMigrateShowPassword.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
views.bootstrapMigrateShowPassword.render(isPasswordVisible)
}
views.bootstrapDescriptionText.text = getString(R.string.bootstrap_migration_enter_backup_password)

View file

@ -62,8 +62,7 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
}
}
private fun downloadRecoveryKey() = withState(sharedViewModel) { _ ->
private fun downloadRecoveryKey() {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "text/plain"

View file

@ -1,32 +0,0 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.crypto.util
import androidx.annotation.DrawableRes
import im.vector.app.R
import im.vector.app.core.extensions.exhaustive
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
@DrawableRes
fun RoomEncryptionTrustLevel?.toImageRes(): Int {
return when (this) {
null -> 0
RoomEncryptionTrustLevel.Default -> R.drawable.ic_shield_black
RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning
RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted
}.exhaustive
}

View file

@ -92,13 +92,11 @@ class IncomingVerificationRequestHandler @Inject constructor(
}
addButton(
context.getString(R.string.ignore),
Runnable {
tx.cancel()
}
{ tx.cancel() }
)
addButton(
context.getString(R.string.action_open),
Runnable {
{
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId)
}

View file

@ -24,7 +24,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.fragmentViewModel
@ -49,6 +48,7 @@ import im.vector.app.features.crypto.verification.request.VerificationRequestFra
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.settings.VectorSettingsActivity
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
@ -162,23 +162,22 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetV
if (state.sasTransactionState == VerificationTxState.Verified
|| state.qrTransactionState == VerificationTxState.Verified
|| state.verifiedFromPrivateKeys) {
views.otherUserShield.setImageResource(R.drawable.ic_shield_trusted)
views.otherUserShield.render(RoomEncryptionTrustLevel.Trusted)
} else {
views.otherUserShield.setImageResource(R.drawable.ic_shield_warning)
views.otherUserShield.render(RoomEncryptionTrustLevel.Warning)
}
views.otherUserNameText.text = getString(
if (state.selfVerificationMode) R.string.crosssigning_verify_this_session else R.string.crosssigning_verify_session
)
views.otherUserShield.isVisible = true
} else {
avatarRenderer.render(matrixItem, views.otherUserAvatarImageView)
if (state.sasTransactionState == VerificationTxState.Verified || state.qrTransactionState == VerificationTxState.Verified) {
views.otherUserNameText.text = getString(R.string.verification_verified_user, matrixItem.getBestName())
views.otherUserShield.isVisible = true
views.otherUserShield.render(RoomEncryptionTrustLevel.Trusted)
} else {
views.otherUserNameText.text = getString(R.string.verification_verify_user, matrixItem.getBestName())
views.otherUserShield.isVisible = false
views.otherUserShield.render(null)
}
}
}

View file

@ -25,6 +25,7 @@ import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationA
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
import im.vector.app.features.html.EventHtmlRenderer
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import javax.inject.Inject
class VerificationConclusionController @Inject constructor(
@ -56,7 +57,7 @@ class VerificationConclusionController @Inject constructor(
bottomSheetVerificationBigImageItem {
id("image")
imageRes(R.drawable.ic_shield_trusted)
roomEncryptionTrustLevel(RoomEncryptionTrustLevel.Trusted)
}
bottomDone()
@ -69,7 +70,7 @@ class VerificationConclusionController @Inject constructor(
bottomSheetVerificationBigImageItem {
id("image")
imageRes(R.drawable.ic_shield_warning)
roomEncryptionTrustLevel(RoomEncryptionTrustLevel.Warning)
}
bottomSheetVerificationNoticeItem {

View file

@ -16,13 +16,13 @@
*/
package im.vector.app.features.crypto.verification.epoxy
import android.widget.ImageView
import androidx.core.view.ViewCompat
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.ui.views.ShieldImageView
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
/**
* A action for bottom sheet.
@ -31,24 +31,14 @@ import im.vector.app.core.epoxy.VectorEpoxyModel
abstract class BottomSheetVerificationBigImageItem : VectorEpoxyModel<BottomSheetVerificationBigImageItem.Holder>() {
@EpoxyAttribute
var imageRes: Int = 0
@EpoxyAttribute
var contentDescription: String? = null
lateinit var roomEncryptionTrustLevel: RoomEncryptionTrustLevel
override fun bind(holder: Holder) {
super.bind(holder)
holder.image.setImageResource(imageRes)
if (contentDescription == null) {
ViewCompat.setImportantForAccessibility(holder.image, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO)
} else {
ViewCompat.setImportantForAccessibility(holder.image, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES)
holder.image.contentDescription = contentDescription
}
holder.image.render(roomEncryptionTrustLevel)
}
class Holder : VectorEpoxyHolder() {
val image by bind<ImageView>(R.id.itemVerificationBigImage)
val image by bind<ShieldImageView>(R.id.itemVerificationBigImage)
}
}

View file

@ -23,6 +23,7 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import javax.inject.Inject
class VerificationQRWaitingController @Inject constructor(
@ -49,7 +50,7 @@ class VerificationQRWaitingController @Inject constructor(
bottomSheetVerificationBigImageItem {
id("image")
imageRes(R.drawable.ic_shield_trusted)
roomEncryptionTrustLevel(RoomEncryptionTrustLevel.Trusted)
}
bottomSheetVerificationWaitingItem {

View file

@ -25,6 +25,7 @@ import im.vector.app.features.crypto.verification.VerificationBottomSheetViewSta
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import javax.inject.Inject
class VerificationQrScannedByOtherController @Inject constructor(
@ -58,7 +59,7 @@ class VerificationQrScannedByOtherController @Inject constructor(
bottomSheetVerificationBigImageItem {
id("image")
imageRes(R.drawable.ic_shield_trusted)
roomEncryptionTrustLevel(RoomEncryptionTrustLevel.Trusted)
}
dividerItem {

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.devtools
interface DevToolsInteractionListener {
fun processAction(action: RoomDevToolAction)
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.devtools
import im.vector.app.core.platform.VectorViewEvents
sealed class DevToolsViewEvents : VectorViewEvents {
object Dismiss : DevToolsViewEvents()
// object ShowStateList : DevToolsViewEvents()
data class ShowAlertMessage(val message: String) : DevToolsViewEvents()
data class ShowSnackMessage(val message: String) : DevToolsViewEvents()
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.devtools
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.events.model.Event
sealed class RoomDevToolAction : VectorViewModelAction {
object ExploreRoomState : RoomDevToolAction()
object OnBackPressed : RoomDevToolAction()
object MenuEdit : RoomDevToolAction()
object MenuItemSend : RoomDevToolAction()
data class ShowStateEvent(val event: Event) : RoomDevToolAction()
data class ShowStateEventType(val stateEventType: String) : RoomDevToolAction()
data class UpdateContentText(val contentJson: String) : RoomDevToolAction()
data class SendCustomEvent(val isStateEvent: Boolean) : RoomDevToolAction()
data class CustomEventTypeChange(val type: String) : RoomDevToolAction()
data class CustomEventContentChange(val content: String) : RoomDevToolAction()
data class CustomEventStateKeyChange(val stateKey: String) : RoomDevToolAction()
}

View file

@ -0,0 +1,256 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.devtools
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import androidx.core.view.forEach
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.viewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.createJSonViewerStyleProvider
import kotlinx.parcelize.Parcelize
import org.billcarsonfr.jsonviewer.JSonViewerFragment
import javax.inject.Inject
class RoomDevToolActivity : SimpleFragmentActivity(), RoomDevToolViewModel.Factory,
FragmentManager.OnBackStackChangedListener {
@Inject lateinit var viewModelFactory: RoomDevToolViewModel.Factory
@Inject lateinit var colorProvider: ColorProvider
// private lateinit var viewModel: RoomDevToolViewModel
private val viewModel: RoomDevToolViewModel by viewModel()
override fun getTitleRes() = R.string.dev_tools_menu_name
override fun getMenuRes() = R.menu.menu_devtools
private var currentDisplayMode: RoomDevToolViewState.Mode? = null
@Parcelize
data class Args(
val roomId: String
) : Parcelable
override fun injectWith(injector: ScreenComponent) {
super.injectWith(injector)
injector.inject(this)
}
override fun create(initialState: RoomDevToolViewState): RoomDevToolViewModel {
return viewModelFactory.create(initialState)
}
override fun initUiAndData() {
super.initUiAndData()
viewModel.subscribe(this) {
renderState(it)
}
viewModel.observeViewEvents {
when (it) {
DevToolsViewEvents.Dismiss -> finish()
is DevToolsViewEvents.ShowAlertMessage -> {
AlertDialog.Builder(this)
.setMessage(it.message)
.setPositiveButton(R.string.ok, null)
.show()
Unit
}
is DevToolsViewEvents.ShowSnackMessage -> showSnackbar(it.message)
}.exhaustive
}
supportFragmentManager.addOnBackStackChangedListener(this)
}
private fun renderState(it: RoomDevToolViewState) {
if (it.displayMode != currentDisplayMode) {
when (it.displayMode) {
RoomDevToolViewState.Mode.Root -> {
val classJava = RoomDevToolFragment::class.java
val tag = classJava.name
if (supportFragmentManager.findFragmentByTag(tag) == null) {
replaceFragment(R.id.container, RoomDevToolFragment::class.java)
} else {
supportFragmentManager.popBackStack()
}
}
RoomDevToolViewState.Mode.StateEventDetail -> {
val frag = JSonViewerFragment.newInstance(
jsonString = it.selectedEventJson ?: "",
initialOpenDepth = -1,
wrap = true,
styleProvider = createJSonViewerStyleProvider(colorProvider)
)
navigateTo(frag)
}
RoomDevToolViewState.Mode.StateEventList,
RoomDevToolViewState.Mode.StateEventListByType -> {
val frag = createFragment(RoomDevToolStateEventListFragment::class.java, Bundle().toMvRxBundle())
navigateTo(frag)
}
RoomDevToolViewState.Mode.EditEventContent -> {
val frag = createFragment(RoomDevToolEditFragment::class.java, Bundle().toMvRxBundle())
navigateTo(frag)
}
is RoomDevToolViewState.Mode.SendEventForm -> {
val frag = createFragment(RoomDevToolSendFormFragment::class.java, Bundle().toMvRxBundle())
navigateTo(frag)
}
}
currentDisplayMode = it.displayMode
invalidateOptionsMenu()
}
when (it.modalLoading) {
is Loading -> showWaitingView()
is Success -> hideWaitingView()
is Fail -> {
hideWaitingView()
}
Uninitialized -> {
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
if (item.itemId == R.id.menuItemEdit) {
viewModel.handle(RoomDevToolAction.MenuEdit)
return true
}
if (item.itemId == R.id.menuItemSend) {
viewModel.handle(RoomDevToolAction.MenuItemSend)
return true
}
return super.onOptionsItemSelected(item)
}
override fun onBackPressed() {
viewModel.handle(RoomDevToolAction.OnBackPressed)
}
private fun navigateTo(fragment: Fragment) {
val tag = fragment.javaClass.name
if (supportFragmentManager.findFragmentByTag(tag) == null) {
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
.replace(R.id.container, fragment, tag)
.addToBackStack(tag)
.commit()
} else {
if (!supportFragmentManager.popBackStackImmediate(tag, 0)) {
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
.replace(R.id.container, fragment, tag)
.addToBackStack(tag)
.commit()
}
}
}
override fun onDestroy() {
supportFragmentManager.removeOnBackStackChangedListener(this)
currentDisplayMode = null
super.onDestroy()
}
override fun onPrepareOptionsMenu(menu: Menu?): Boolean = withState(viewModel) { state ->
menu?.forEach {
val isVisible = when (it.itemId) {
R.id.menuItemEdit -> {
state.displayMode is RoomDevToolViewState.Mode.StateEventDetail
}
R.id.menuItemSend -> {
state.displayMode is RoomDevToolViewState.Mode.EditEventContent
|| state.displayMode is RoomDevToolViewState.Mode.SendEventForm
}
else -> true
}
it.isVisible = isVisible
}
return@withState true
}
companion object {
fun intent(context: Context, roomId: String): Intent {
return Intent(context, RoomDevToolActivity::class.java).apply {
putExtra(MvRx.KEY_ARG, Args(roomId))
}
}
}
override fun onBackStackChanged() = withState(viewModel) { state ->
updateToolBar(state)
}
private fun updateToolBar(state: RoomDevToolViewState) {
val title = when (state.displayMode) {
RoomDevToolViewState.Mode.Root -> {
getString(getTitleRes())
}
RoomDevToolViewState.Mode.StateEventList -> {
getString(R.string.dev_tools_state_event)
}
RoomDevToolViewState.Mode.StateEventDetail -> {
state.selectedEvent?.type
}
RoomDevToolViewState.Mode.EditEventContent -> {
getString(R.string.dev_tools_edit_content)
}
RoomDevToolViewState.Mode.StateEventListByType -> {
state.currentStateType ?: ""
}
is RoomDevToolViewState.Mode.SendEventForm -> {
getString(
if (state.displayMode.isState) R.string.dev_tools_send_custom_state_event
else R.string.dev_tools_send_custom_event
)
}
}
supportActionBar?.let {
it.title = title
} ?: run {
setTitle(title)
}
invalidateOptionsMenu()
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.devtools
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentDevtoolsEditorBinding
import javax.inject.Inject
class RoomDevToolEditFragment @Inject constructor()
: VectorBaseFragment<FragmentDevtoolsEditorBinding>() {
private val sharedViewModel: RoomDevToolViewModel by activityViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentDevtoolsEditorBinding {
return FragmentDevtoolsEditorBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
withState(sharedViewModel) {
views.editText.setText(it.editedContent ?: "{}")
}
views.editText.textChanges()
.skipInitialValue()
.subscribe {
sharedViewModel.handle(RoomDevToolAction.UpdateContentText(it.toString()))
}
.disposeOnDestroyView()
}
override fun onResume() {
super.onResume()
views.editText.requestFocus()
}
override fun onStop() {
super.onStop()
views.editText.hideKeyboard()
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.devtools
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.activityViewModel
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import javax.inject.Inject
class RoomDevToolFragment @Inject constructor(
private val epoxyController: RoomDevToolRootController
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(),
DevToolsInteractionListener {
private val sharedViewModel: RoomDevToolViewModel by activityViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding {
return FragmentGenericRecyclerBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.genericRecyclerView.configureWith(epoxyController, showDivider = true)
epoxyController.interactionListener = this
// sharedViewModel.observeViewEvents {
// when (it) {
// is DevToolsViewEvents.showJson -> {
// JSonViewerDialog.newInstance(it.jsonString, -1, createJSonViewerStyleProvider(colorProvider))
// .show(childFragmentManager, "JSON_VIEWER")
//
// }
// }
// }
}
override fun onDestroyView() {
views.genericRecyclerView.cleanup()
epoxyController.interactionListener = null
super.onDestroyView()
}
override fun processAction(action: RoomDevToolAction) {
sharedViewModel.handle(action)
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.devtools
import android.view.View
import com.airbnb.epoxy.EpoxyController
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericButtonItem
import javax.inject.Inject
class RoomDevToolRootController @Inject constructor(
private val stringProvider: StringProvider
) : EpoxyController() {
init {
requestModelBuild()
}
var interactionListener: DevToolsInteractionListener? = null
override fun buildModels() {
genericButtonItem {
id("explore")
text(stringProvider.getString(R.string.dev_tools_explore_room_state))
buttonClickAction(View.OnClickListener {
interactionListener?.processAction(RoomDevToolAction.ExploreRoomState)
})
}
genericButtonItem {
id("send")
text(stringProvider.getString(R.string.dev_tools_send_custom_event))
buttonClickAction(View.OnClickListener {
interactionListener?.processAction(RoomDevToolAction.SendCustomEvent(false))
})
}
genericButtonItem {
id("send_state")
text(stringProvider.getString(R.string.dev_tools_send_state_event))
buttonClickAction(View.OnClickListener {
interactionListener?.processAction(RoomDevToolAction.SendCustomEvent(true))
})
}
}
}

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.devtools
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.features.form.formEditTextItem
import im.vector.app.features.form.formMultiLineEditTextItem
import javax.inject.Inject
class RoomDevToolSendFormController @Inject constructor(
private val stringProvider: StringProvider
) : TypedEpoxyController<RoomDevToolViewState>() {
var interactionListener: DevToolsInteractionListener? = null
override fun buildModels(data: RoomDevToolViewState?) {
val sendEventForm = (data?.displayMode as? RoomDevToolViewState.Mode.SendEventForm) ?: return
genericFooterItem {
id("topSpace")
text("")
}
formEditTextItem {
id("event_type")
enabled(true)
value(data.sendEventDraft?.type)
hint(stringProvider.getString(R.string.dev_tools_form_hint_type))
showBottomSeparator(false)
onTextChange { text ->
interactionListener?.processAction(RoomDevToolAction.CustomEventTypeChange(text))
}
}
if (sendEventForm.isState) {
formEditTextItem {
id("state_key")
enabled(true)
value(data.sendEventDraft?.stateKey)
hint(stringProvider.getString(R.string.dev_tools_form_hint_state_key))
showBottomSeparator(false)
onTextChange { text ->
interactionListener?.processAction(RoomDevToolAction.CustomEventStateKeyChange(text))
}
}
}
formMultiLineEditTextItem {
id("event_content")
enabled(true)
value(data.sendEventDraft?.content)
hint(stringProvider.getString(R.string.dev_tools_form_hint_event_content))
showBottomSeparator(false)
onTextChange { text ->
interactionListener?.processAction(RoomDevToolAction.CustomEventContentChange(text))
}
}
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.devtools
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import javax.inject.Inject
class RoomDevToolSendFormFragment @Inject constructor(
private val epoxyController: RoomDevToolSendFormController
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(), DevToolsInteractionListener {
val sharedViewModel: RoomDevToolViewModel by activityViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding {
return FragmentGenericRecyclerBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.genericRecyclerView.configureWith(epoxyController, showDivider = false)
epoxyController.interactionListener = this
}
override fun onDestroyView() {
views.genericRecyclerView.cleanup()
epoxyController.interactionListener = null
super.onDestroyView()
}
override fun invalidate() = withState(sharedViewModel) { state ->
epoxyController.setData(state)
}
override fun processAction(action: RoomDevToolAction) {
sharedViewModel.handle(action)
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.devtools
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import javax.inject.Inject
class RoomDevToolStateEventListFragment @Inject constructor(
private val epoxyController: RoomStateListController
) : VectorBaseFragment<FragmentGenericRecyclerBinding>(), DevToolsInteractionListener {
val sharedViewModel: RoomDevToolViewModel by activityViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding {
return FragmentGenericRecyclerBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.genericRecyclerView.configureWith(epoxyController, showDivider = true)
epoxyController.interactionListener = this
}
override fun onDestroyView() {
views.genericRecyclerView.cleanup()
epoxyController.interactionListener = null
super.onDestroyView()
}
override fun invalidate() = withState(sharedViewModel) { state ->
epoxyController.setData(state)
}
override fun processAction(action: RoomDevToolAction) {
sharedViewModel.handle(action)
}
}

View file

@ -0,0 +1,304 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.devtools
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
import com.squareup.moshi.Types
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import kotlinx.coroutines.launch
import org.json.JSONObject
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.rx.rx
class RoomDevToolViewModel @AssistedInject constructor(
@Assisted val initialState: RoomDevToolViewState,
private val errorFormatter: ErrorFormatter,
private val stringProvider: StringProvider,
private val session: Session
) : VectorViewModel<RoomDevToolViewState, RoomDevToolAction, DevToolsViewEvents>(initialState) {
@AssistedFactory
interface Factory {
fun create(initialState: RoomDevToolViewState): RoomDevToolViewModel
}
companion object : MvRxViewModelFactory<RoomDevToolViewModel, RoomDevToolViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomDevToolViewState): RoomDevToolViewModel {
val factory = when (viewModelContext) {
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
is ActivityViewModelContext -> viewModelContext.activity as? Factory
}
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
}
}
init {
session.getRoom(initialState.roomId)
?.rx()
?.liveStateEvents(emptySet())
?.execute { async ->
copy(stateEvents = async)
}
}
override fun handle(action: RoomDevToolAction) {
when (action) {
RoomDevToolAction.ExploreRoomState -> {
setState {
copy(
displayMode = RoomDevToolViewState.Mode.StateEventList,
selectedEvent = null
)
}
}
is RoomDevToolAction.ShowStateEvent -> {
val jsonString = MoshiProvider.providesMoshi()
.adapter(Event::class.java)
.toJson(action.event)
setState {
copy(
displayMode = RoomDevToolViewState.Mode.StateEventDetail,
selectedEvent = action.event,
selectedEventJson = jsonString
)
}
}
RoomDevToolAction.OnBackPressed -> {
handleBack()
}
RoomDevToolAction.MenuEdit -> {
withState {
if (it.displayMode == RoomDevToolViewState.Mode.StateEventDetail) {
// we want to edit it
val content = it.selectedEvent?.content?.let { JSONObject(it).toString(4) } ?: "{\n\t\n}"
setState {
copy(
editedContent = content,
displayMode = RoomDevToolViewState.Mode.EditEventContent
)
}
}
}
}
is RoomDevToolAction.ShowStateEventType -> {
setState {
copy(
displayMode = RoomDevToolViewState.Mode.StateEventListByType,
currentStateType = action.stateEventType
)
}
}
RoomDevToolAction.MenuItemSend -> {
handleMenuItemSend()
}
is RoomDevToolAction.UpdateContentText -> {
setState {
copy(editedContent = action.contentJson)
}
}
is RoomDevToolAction.SendCustomEvent -> {
setState {
copy(
displayMode = RoomDevToolViewState.Mode.SendEventForm(action.isStateEvent),
sendEventDraft = RoomDevToolViewState.SendEventDraft(EventType.MESSAGE, null, "{\n}")
)
}
}
is RoomDevToolAction.CustomEventTypeChange -> {
setState {
copy(
sendEventDraft = sendEventDraft?.copy(type = action.type)
)
}
}
is RoomDevToolAction.CustomEventStateKeyChange -> {
setState {
copy(
sendEventDraft = sendEventDraft?.copy(stateKey = action.stateKey)
)
}
}
is RoomDevToolAction.CustomEventContentChange -> {
setState {
copy(
sendEventDraft = sendEventDraft?.copy(content = action.content)
)
}
}
}
}
private fun handleMenuItemSend() = withState { state ->
when (state.displayMode) {
RoomDevToolViewState.Mode.EditEventContent -> editEventContent(state)
is RoomDevToolViewState.Mode.SendEventForm -> sendEventContent(state, state.displayMode.isState)
else -> Unit
}
}
private fun editEventContent(state: RoomDevToolViewState) {
setState { copy(modalLoading = Loading()) }
viewModelScope.launch {
try {
val room = session.getRoom(initialState.roomId)
?: throw IllegalArgumentException(stringProvider.getString(R.string.room_error_not_found))
val adapter = MoshiProvider.providesMoshi()
.adapter<JsonDict>(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
val json = adapter.fromJson(state.editedContent ?: "")
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_content))
room.sendStateEvent(
state.selectedEvent?.type ?: "",
state.selectedEvent?.stateKey,
json
)
_viewEvents.post(DevToolsViewEvents.ShowSnackMessage(stringProvider.getString(R.string.dev_tools_success_state_event)))
setState {
copy(
modalLoading = Success(Unit),
selectedEventJson = null,
editedContent = null,
displayMode = RoomDevToolViewState.Mode.StateEventListByType
)
}
} catch (failure: Throwable) {
_viewEvents.post(DevToolsViewEvents.ShowAlertMessage(errorFormatter.toHumanReadable(failure)))
setState { copy(modalLoading = Fail(failure)) }
}
}
}
private fun sendEventContent(state: RoomDevToolViewState, isState: Boolean) {
setState { copy(modalLoading = Loading()) }
viewModelScope.launch {
try {
val room = session.getRoom(initialState.roomId)
?: throw IllegalArgumentException(stringProvider.getString(R.string.room_error_not_found))
val adapter = MoshiProvider.providesMoshi()
.adapter<JsonDict>(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
val json = adapter.fromJson(state.sendEventDraft?.content ?: "")
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_content))
val eventType = state.sendEventDraft?.type
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_message_type))
if (isState) {
room.sendStateEvent(
eventType,
state.sendEventDraft.stateKey,
json
)
} else {
// can we try to do some validation??
// val validParse = MoshiProvider.providesMoshi().adapter(MessageContent::class.java).fromJson(it.sendEventDraft.content ?: "")
json.toModel<MessageContent>(catchError = false)
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_malformed_event))
room.sendEvent(
eventType,
json
)
}
_viewEvents.post(DevToolsViewEvents.ShowSnackMessage(stringProvider.getString(R.string.dev_tools_success_event)))
setState {
copy(
modalLoading = Success(Unit),
sendEventDraft = null,
displayMode = RoomDevToolViewState.Mode.Root
)
}
} catch (failure: Throwable) {
_viewEvents.post(DevToolsViewEvents.ShowAlertMessage(errorFormatter.toHumanReadable(failure)))
setState { copy(modalLoading = Fail(failure)) }
}
}
}
private fun handleBack() = withState {
when (it.displayMode) {
RoomDevToolViewState.Mode.Root -> {
_viewEvents.post(DevToolsViewEvents.Dismiss)
}
RoomDevToolViewState.Mode.StateEventList -> {
setState {
copy(
selectedEvent = null,
selectedEventJson = null,
displayMode = RoomDevToolViewState.Mode.Root
)
}
}
RoomDevToolViewState.Mode.StateEventDetail -> {
setState {
copy(
selectedEvent = null,
selectedEventJson = null,
displayMode = RoomDevToolViewState.Mode.StateEventListByType
)
}
}
RoomDevToolViewState.Mode.EditEventContent -> {
setState {
copy(
displayMode = RoomDevToolViewState.Mode.StateEventDetail
)
}
}
RoomDevToolViewState.Mode.StateEventListByType -> {
setState {
copy(
currentStateType = null,
displayMode = RoomDevToolViewState.Mode.StateEventList
)
}
}
is RoomDevToolViewState.Mode.SendEventForm -> {
setState {
copy(
displayMode = RoomDevToolViewState.Mode.Root
)
}
}
}
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.devtools
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.api.session.events.model.Event
data class RoomDevToolViewState(
val roomId: String = "",
val displayMode: Mode = Mode.Root,
val stateEvents: Async<List<Event>> = Uninitialized,
val currentStateType: String? = null,
val selectedEvent: Event? = null,
val selectedEventJson: String? = null,
val editedContent: String? = null,
val modalLoading: Async<Unit> = Uninitialized,
val sendEventDraft: SendEventDraft? = null
) : MvRxState {
constructor(args: RoomDevToolActivity.Args) : this(roomId = args.roomId, displayMode = Mode.Root)
sealed class Mode {
object Root : Mode()
object StateEventList : Mode()
object StateEventListByType : Mode()
object StateEventDetail : Mode()
object EditEventContent : Mode()
data class SendEventForm(val isState: Boolean) : Mode()
}
data class SendEventDraft(
val type: String?,
val stateKey: String?,
val content: String?
)
}

View file

@ -0,0 +1,109 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.devtools
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.R
import im.vector.app.core.epoxy.noResultItem
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.GenericItem
import im.vector.app.core.ui.list.genericItem
import me.gujun.android.span.span
import org.json.JSONObject
import javax.inject.Inject
class RoomStateListController @Inject constructor(
private val stringProvider: StringProvider,
private val colorProvider: ColorProvider
) : TypedEpoxyController<RoomDevToolViewState>() {
var interactionListener: DevToolsInteractionListener? = null
override fun buildModels(data: RoomDevToolViewState?) {
when (data?.displayMode) {
RoomDevToolViewState.Mode.StateEventList -> {
val stateEventsGroups = data.stateEvents.invoke().orEmpty().groupBy { it.type }
if (stateEventsGroups.isEmpty()) {
noResultItem {
id("no state events")
text(stringProvider.getString(R.string.no_result_placeholder))
}
} else {
stateEventsGroups.forEach { entry ->
genericItem {
id(entry.key)
title(entry.key)
description(stringProvider.getQuantityString(R.plurals.entries, entry.value.size, entry.value.size))
itemClickAction(GenericItem.Action("view").apply {
perform = Runnable {
interactionListener?.processAction(RoomDevToolAction.ShowStateEventType(entry.key))
}
})
}
}
}
}
RoomDevToolViewState.Mode.StateEventListByType -> {
val stateEvents = data.stateEvents.invoke().orEmpty().filter { it.type == data.currentStateType }
if (stateEvents.isEmpty()) {
noResultItem {
id("no state events")
text(stringProvider.getString(R.string.no_result_placeholder))
}
} else {
stateEvents.forEach { stateEvent ->
val contentJson = JSONObject(stateEvent.content.orEmpty()).toString().let {
if (it.length > 140) {
it.take(140) + Typography.ellipsis
} else {
it.take(140)
}
}
genericItem {
id(stateEvent.eventId)
title(span {
+"Type: "
span {
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
text = "\"${stateEvent.type}\""
textStyle = "normal"
}
+"\nState Key: "
span {
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
text = stateEvent.stateKey.let { "\"$it\"" }
textStyle = "normal"
}
})
description(contentJson)
itemClickAction(GenericItem.Action("view").apply {
perform = Runnable {
interactionListener?.processAction(RoomDevToolAction.ShowStateEvent(stateEvent))
}
})
}
}
}
}
else -> {
// nop
}
}
}
}

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.form
import android.graphics.Typeface
import android.text.Editable
import android.view.View
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.extensions.setTextSafe
import im.vector.app.core.platform.SimpleTextWatcher
@EpoxyModelClass(layout = R.layout.item_form_multiline_text_input)
abstract class FormMultiLineEditTextItem : VectorEpoxyModel<FormMultiLineEditTextItem.Holder>() {
@EpoxyAttribute
var hint: String? = null
@EpoxyAttribute
var value: String? = null
@EpoxyAttribute
var showBottomSeparator: Boolean = true
@EpoxyAttribute
var errorMessage: String? = null
@EpoxyAttribute
var enabled: Boolean = true
@EpoxyAttribute
var textSizeSp: Int? = null
@EpoxyAttribute
var minLines: Int = 3
@EpoxyAttribute
var typeFace: Typeface = Typeface.DEFAULT
@EpoxyAttribute
var onTextChange: ((String) -> Unit)? = null
private val onTextChangeListener = object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) {
onTextChange?.invoke(s.toString())
}
}
override fun bind(holder: Holder) {
super.bind(holder)
holder.textInputLayout.isEnabled = enabled
holder.textInputLayout.hint = hint
holder.textInputLayout.error = errorMessage
holder.textInputEditText.typeface = typeFace
holder.textInputEditText.textSize = textSizeSp?.toFloat() ?: 12f
holder.textInputEditText.minLines = minLines
// Update only if text is different and value is not null
holder.textInputEditText.setTextSafe(value)
holder.textInputEditText.isEnabled = enabled
holder.textInputEditText.addTextChangedListener(onTextChangeListener)
holder.bottomSeparator.isVisible = showBottomSeparator
}
override fun shouldSaveViewState(): Boolean {
return false
}
override fun unbind(holder: Holder) {
super.unbind(holder)
holder.textInputEditText.removeTextChangedListener(onTextChangeListener)
}
class Holder : VectorEpoxyHolder() {
val textInputLayout by bind<TextInputLayout>(R.id.formMultiLineTextInputLayout)
val textInputEditText by bind<TextInputEditText>(R.id.formMultiLineEditText)
val bottomSeparator by bind<View>(R.id.formTextInputDivider)
}
}

View file

@ -29,7 +29,6 @@ import im.vector.app.R
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
@ -123,7 +122,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
session
.rx()
.liveGroupSummaries(groupSummariesQueryParams),
BiFunction { allCommunityGroup, communityGroups ->
{ allCommunityGroup, communityGroups ->
listOf(allCommunityGroup) + communityGroups
}
)

Some files were not shown because too many files have changed in this diff Show more