Merge branch 'develop' into feature/fga/fix_some_voip_issues
This commit is contained in:
commit
776ebce497
@ -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)
|
||||
|
2
fastlane/metadata/android/ar/changelogs/40100100.txt
Normal file
2
fastlane/metadata/android/ar/changelogs/40100100.txt
Normal file
@ -0,0 +1,2 @@
|
||||
يحتوي هذا الإصدار الجديد بشكل أساسي على إصلاحات للأخطاء وتحسينات. إرسال الرسالة أصبح الآن أسرع بكثير.
|
||||
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
2
fastlane/metadata/android/ar/changelogs/40100110.txt
Normal file
2
fastlane/metadata/android/ar/changelogs/40100110.txt
Normal file
@ -0,0 +1,2 @@
|
||||
يحتوي هذا الإصدار الجديد بشكل أساسي على تحسينات في واجهة المستخدم وتجربة المستخدم. يُمكنك الآن دعوة الأصدقاء وإنشاء رسالة مُباشرة بسرعة كبيرة عن طريق مسح رموز الاستجابة السريعة.
|
||||
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
2
fastlane/metadata/android/ar/changelogs/40100120.txt
Normal file
2
fastlane/metadata/android/ar/changelogs/40100120.txt
Normal file
@ -0,0 +1,2 @@
|
||||
التغييرات الرئيسة في هذا الإصدار: مُعاينة URL، لوحة مفاتيح Emoji جديدة، إمكانيات جديدة لإعدادات الغرفة والثلج لميلاد المسيح!
|
||||
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.12
|
2
fastlane/metadata/android/ar/changelogs/40100130.txt
Normal file
2
fastlane/metadata/android/ar/changelogs/40100130.txt
Normal file
@ -0,0 +1,2 @@
|
||||
التغييرات الرئيسة في هذا الإصدار: مُعاينة URL، لوحة مفاتيح Emoji جديدة، إمكانيات جديدة لإعدادات الغرفة والثلج لميلاد المسيح!
|
||||
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.13
|
2
fastlane/metadata/android/ar/changelogs/40100140.txt
Normal file
2
fastlane/metadata/android/ar/changelogs/40100140.txt
Normal file
@ -0,0 +1,2 @@
|
||||
التغييرات الرئيسة في هذا الإصدار: تحرير أذونات الغُرفة، السِّمة التلقائية الفاتحة/الداكنة، ومجموعة من إصلاحات الأخطاء.
|
||||
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.14
|
2
fastlane/metadata/android/ar/changelogs/40100150.txt
Normal file
2
fastlane/metadata/android/ar/changelogs/40100150.txt
Normal file
@ -0,0 +1,2 @@
|
||||
التغييرات الرئيسة في هذا الإصدار: دعم تسجيل الدخول الاجتماعي.
|
||||
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.15
|
2
fastlane/metadata/android/ar/changelogs/40100160.txt
Normal file
2
fastlane/metadata/android/ar/changelogs/40100160.txt
Normal 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
|
2
fastlane/metadata/android/ar/changelogs/40100170.txt
Normal file
2
fastlane/metadata/android/ar/changelogs/40100170.txt
Normal file
@ -0,0 +1,2 @@
|
||||
التغييرات الرئيسة في هذا الإصدار: إصلاحات الأخطاء!
|
||||
سجل التعديل الكامل: https://github.com/vector-im/element-android/releases/tag/v1.0.17
|
31
fastlane/metadata/android/ar/full_description.txt
Normal file
31
fastlane/metadata/android/ar/full_description.txt
Normal 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.
|
1
fastlane/metadata/android/ar/short_description.txt
Normal file
1
fastlane/metadata/android/ar/short_description.txt
Normal file
@ -0,0 +1 @@
|
||||
مُحادثة آمنة لا مركزية و VoIP. حافظ على بياناتك آمنة من الأطراف الثالثة.
|
1
fastlane/metadata/android/ar/title.txt
Normal file
1
fastlane/metadata/android/ar/title.txt
Normal file
@ -0,0 +1 @@
|
||||
Element (سابقاً Riot.im)
|
2
fastlane/metadata/android/ca/changelogs/40100170.txt
Normal file
2
fastlane/metadata/android/ca/changelogs/40100170.txt
Normal 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
|
2
fastlane/metadata/android/de/changelogs/40100170.txt
Normal file
2
fastlane/metadata/android/de/changelogs/40100170.txt
Normal 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
|
2
fastlane/metadata/android/et/changelogs/40100170.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40100170.txt
Normal 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
|
@ -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
|
||||
|
2
fastlane/metadata/android/it/changelogs/40100140.txt
Normal file
2
fastlane/metadata/android/it/changelogs/40100140.txt
Normal 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
|
2
fastlane/metadata/android/it/changelogs/40100150.txt
Normal file
2
fastlane/metadata/android/it/changelogs/40100150.txt
Normal 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
|
2
fastlane/metadata/android/it/changelogs/40100160.txt
Normal file
2
fastlane/metadata/android/it/changelogs/40100160.txt
Normal 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
|
2
fastlane/metadata/android/it/changelogs/40100170.txt
Normal file
2
fastlane/metadata/android/it/changelogs/40100170.txt
Normal 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
|
2
fastlane/metadata/android/sk/changelogs/40100120.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40100120.txt
Normal 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
|
2
fastlane/metadata/android/sk/changelogs/40100130.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40100130.txt
Normal 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
|
2
fastlane/metadata/android/sk/changelogs/40100140.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40100140.txt
Normal 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
|
2
fastlane/metadata/android/sk/changelogs/40100150.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40100150.txt
Normal 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
|
2
fastlane/metadata/android/sk/changelogs/40100160.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40100160.txt
Normal 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
|
2
fastlane/metadata/android/sk/changelogs/40100170.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40100170.txt
Normal 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
|
@ -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.
|
||||
|
2
fastlane/metadata/android/sr/changelogs/40100170.txt
Normal file
2
fastlane/metadata/android/sr/changelogs/40100170.txt
Normal 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
|
2
fastlane/metadata/android/uk/changelogs/40100170.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40100170.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Основні зміни у цій версії: Виправлення помилок!
|
||||
Повний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.0.17
|
@ -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)
|
||||
|
@ -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>>
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
</resources>
|
||||
<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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
@ -267,6 +267,7 @@
|
||||
<!-- </intent-filter>-->
|
||||
</activity>
|
||||
|
||||
<activity android:name=".features.devtools.RoomDevToolActivity"/>
|
||||
<!-- Services -->
|
||||
|
||||
<service
|
||||
|
@ -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 <info@dlg.im>
|
||||
</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>
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -48,7 +48,7 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
|
||||
}
|
||||
|
||||
@EpoxyAttribute
|
||||
var title: String? = null
|
||||
var title: CharSequence? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var description: CharSequence? = null
|
||||
|
@ -89,7 +89,7 @@ class KnownCallsViewHolder {
|
||||
this.pipWrapper = pipWrapper
|
||||
this.currentCallsView?.callback = interactionListener
|
||||
pipWrapper.setOnClickListener(
|
||||
DebouncedClickListener({ _ ->
|
||||
DebouncedClickListener({
|
||||
interactionListener.onTapToReturnToCall()
|
||||
})
|
||||
)
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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> {
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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?
|
||||
|
@ -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() }
|
||||
|
@ -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) {
|
||||
|
@ -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 ->
|
||||
|
@ -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() }
|
||||
|
@ -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) {
|
||||
@ -141,8 +139,8 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
|
||||
views.keysBackupSetupStep2Button.setOnClickListener { doNext() }
|
||||
views.keysBackupSetupStep2SkipButton.setOnClickListener { skipPassphrase() }
|
||||
|
||||
views.keysBackupSetupStep2PassphraseEnterEdittext.doOnTextChanged { _, _, _, _ -> onPassphraseChanged() }
|
||||
views.keysBackupSetupStep2PassphraseConfirmEditText.doOnTextChanged { _, _, _, _ -> onConfirmPassphraseChanged() }
|
||||
views.keysBackupSetupStep2PassphraseEnterEdittext.doOnTextChanged { _, _, _, _ -> onPassphraseChanged() }
|
||||
views.keysBackupSetupStep2PassphraseConfirmEditText.doOnTextChanged { _, _, _, _ -> onConfirmPassphraseChanged() }
|
||||
}
|
||||
|
||||
private fun toggleVisibilityMode() {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
@ -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()
|
||||
}
|
@ -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()
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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?
|
||||
)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
Loading…
x
Reference in New Issue
Block a user