Merge branch 'ui-userprofiles-and-misc' into 'master'

UI: User Profiles + General Fixes

See merge request [ryubing/ryujinx!173](https://git.ryujinx.app/ryubing/ryujinx/-/merge_requests/173)
This commit is contained in:
Neo 2026-01-06 15:01:08 -06:00
commit ec2ed8935a
26 changed files with 1398 additions and 1643 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,804 @@
{
"Locales": [
{
"ID": "MenuBarOptions_OpenUserProfiles",
"Translations": {
"ar_SA": "_ملفات المستخدمين",
"de_DE": "_Benutzerprofile",
"el_GR": "_Προφίλ Χρηστών",
"en_US": "_User Profiles",
"es_ES": "_Perfiles de Usuario",
"fr_FR": "_Profils d'Utilisateurs",
"he_IL": "_פרופילי משתמש",
"it_IT": "_Profili utent",
"ja_JP": "ユーザプロファイル(_M)",
"ko_KR": "사용자 프로필(_M)",
"no_NO": "_Brukerprofiler",
"pl_PL": "_Profile użytkowników",
"pt_BR": "_Perfis de usuário",
"ru_RU": "_Учётные записи",
"sv_SE": "_Användarprofiler",
"th_TH": "_โปรไฟล์ผู้ใช้งาน",
"tr_TR": "_Kullanıcı Profilleri",
"uk_UA": "_Профілі користувачів",
"zh_CN": "用户配置文件(_M)",
"zh_TW": "使用者設定檔(_M)"
}
},
{
"ID": "WindowTitle",
"Translations": {
"ar_SA": "ملفات المستخدمين",
"de_DE": "Benutzerprofile",
"el_GR": "Προφίλ Χρηστών",
"en_US": "User Profiles",
"es_ES": "Perfiles de Usuario",
"fr_FR": "Profils d'Utilisateurs",
"he_IL": "פרופילי משתמש",
"it_IT": "Profili utent",
"ja_JP": "ユーザプロファイル",
"ko_KR": "사용자 프로필",
"no_NO": "Brukerprofiler",
"pl_PL": "Profile użytkowników",
"pt_BR": "Perfis de usuário",
"ru_RU": "Учётные записи",
"sv_SE": "Användarprofiler",
"th_TH": "โปรไฟล์ผู้ใช้งาน",
"tr_TR": "Kullanıcı Profilleri",
"uk_UA": "Профілі користувачів",
"zh_CN": "用户配置文件",
"zh_TW": "使用者設定檔"
}
},
{
"ID": "ManageSaves",
"Translations": {
"ar_SA": "عمليات الحفظ",
"de_DE": "Speicherstände",
"el_GR": "Αποθηκεύσεις",
"en_US": "Saves",
"es_ES": "Partidas",
"fr_FR": "Sauvegardes",
"he_IL": "שמירות",
"it_IT": "Salvataggi",
"ja_JP": "セーブデータ",
"ko_KR": "저장",
"no_NO": "Lagringer",
"pl_PL": "Zapisy",
"pt_BR": "Salvamentos",
"ru_RU": "Сохранения",
"sv_SE": "Sparningar",
"th_TH": "บันทึก",
"tr_TR": "Kayıtlar",
"uk_UA": "Збереження",
"zh_CN": "存档",
"zh_TW": "存檔"
}
},
{
"ID": "DeleteSaveNote",
"Translations": {
"ar_SA": "هل حذف بيانات حفظ المستخدم لهذه اللعبة؟",
"de_DE": "Löschen Sie die gespeicherten Spielstände dieses Spiels?",
"el_GR": "Διαγραφή των δεδομένων αποθήκευσης αυτού του παιχνιδιού;",
"en_US": "Delete this game's save data?",
"es_ES": "¿Eliminar los datos de guardado de este juego?",
"fr_FR": "Supprimer les données de sauvegarde de ce jeu ?",
"he_IL": "האם למחוק את נתוני השמירה של המשחק הזה?",
"it_IT": "Eliminare i dati di salvataggio di questo gioco?",
"ja_JP": "このゲームのセーブデータを削除しますか?",
"ko_KR": "이 게임의 저장 데이터를 삭제하시겠습니까?",
"no_NO": "Slette lagrede data for dette spillet?",
"pl_PL": "Usunąć dane zapisu dla tej gry?",
"pt_BR": "Excluir os dados salvos deste jogo?",
"ru_RU": "Удалить данные сохранений для этой игры?",
"sv_SE": "Ta bort sparad data för detta spel?",
"th_TH": "ลบข้อมูลบันทึกของเกมนี้หรือไม่?",
"tr_TR": "Bu oyun için kaydedilen veriyi silmek?",
"uk_UA": "Видалити збереження даних для цієї гри?",
"zh_CN": "删除此游戏的存档数据?",
"zh_TW": "刪除此遊戲的存檔資料?"
}
},
{
"ID": "SaveManagerTitle",
"Translations": {
"ar_SA": "حفظات {0}",
"de_DE": "{0}s Speicherstände",
"el_GR": "Αποθηκεύσεις του {0}",
"en_US": "{0}'s Saves",
"es_ES": "Guardados de {0}",
"fr_FR": "Sauvegardes de {0}",
"he_IL": "שמירות של {0}",
"it_IT": "Salvataggi di {0}",
"ja_JP": "{0} のセーブデータ",
"ko_KR": "{0} 의 저장",
"no_NO": "Lagringer til {0}",
"pl_PL": "Zapisy {0}",
"pt_BR": "Salvamentos de {0}",
"ru_RU": "Сохранения {0}",
"sv_SE": "{0}s Sparningar",
"th_TH": "ข้อมูลที่บันทึกไว้ของ {0}",
"tr_TR": "{0}nin Kayıtları",
"uk_UA": "Збереження {0}",
"zh_CN": "{0} 的存档",
"zh_TW": "{0} 的存檔"
}
},
{
"ID": "RecoverLostProfiles",
"Translations": {
"ar_SA": "الملفات الشخصية المفقودة",
"de_DE": "Verlorene Profile",
"el_GR": "Χαμένα προφίλ",
"en_US": "Lost Profiles",
"es_ES": "Perfiles Perdidos",
"fr_FR": "Profils Perdus",
"he_IL": "פרופילים אבודים",
"it_IT": "Profili persi",
"ja_JP": "失われたプロフィール",
"ko_KR": "분실된 프로필",
"no_NO": "Tapte profiler",
"pl_PL": "Utracone profile",
"pt_BR": "Perfis perdidos",
"ru_RU": "Потерянные учёные записи",
"sv_SE": "Förlorade profiler",
"th_TH": "โปรไฟล์ที่สูญหาย",
"tr_TR": "Kayıp profiller",
"uk_UA": "Втрачені профілі",
"zh_CN": "丢失的个人资料",
"zh_TW": "遺失的個人資料"
}
},
{
"ID": "RecoverLostProfiles_ToolTip",
"Translations": {
"ar_SA": "يستعيد الملفات الشخصية التي لم تُحذف يدويًا والتي تحتوي على حفظات.",
"de_DE": "Stellt nicht manuell gelöschte Profile mit Speicherständen wieder.",
"el_GR": "Ανακτά προφίλ που δεν διαγράφηκαν χειροκίνητα και έχουν αποθηκεύσεις.",
"en_US": "Recovers non-manually-deleted profiles that have saves.",
"es_ES": "Recupera perfiles no eliminados manualmente que tienen guardados.",
"fr_FR": "Récupère les profils non supprimés manuellement ayant des sauvegardes.",
"he_IL": "שחזור פרופילים שלא נמחקו ידנית ויש להם שמירות.",
"it_IT": "Recupera profili non eliminati manualmente che hanno salvataggi.",
"ja_JP": "手動で削除されていない、保存されたプロフィールを回復します。",
"ko_KR": "수동으로 삭제되지 않은 저장된 프로필을 복구합니다.",
"no_NO": "Gjenoppretter profiler som ikke er manuelt slettet og som har lagringer.",
"pl_PL": "Odzyskuje profile, które nie zostały usunięte ręcznie, a które mają zapisy.",
"pt_BR": "Recupera perfis não deletados manualmente que possuem saves.",
"ru_RU": "Восстанавливает учётные записи, не удалённые вручную и имеющие сохранения.",
"sv_SE": "Återställer profiler som inte har raderats manuellt och har sparade data.",
"th_TH": "กู้คืนโปรไฟล์ที่ไม่ได้ลบด้วยตนเองและมีการบันทึก",
"tr_TR": "Manuel olarak silinmemiş ve kayıtlara sahip profilleri kurtarır.",
"uk_UA": "Відновлює учётні записи, які не були видалені вручну і мають збереження.",
"zh_CN": "恢复未手动删除且有存档的个人资料。",
"zh_TW": "恢復未手動刪除且有存檔的個人資料。"
}
},
{
"ID": "RecoverProfile",
"Translations": {
"ar_SA": "استعادة",
"de_DE": "Wiederherstellen",
"el_GR": "Ανάκτηση",
"en_US": "Recover",
"es_ES": "Recuperar",
"fr_FR": "Récupérer",
"he_IL": "שחזר",
"it_IT": "Recupera",
"ja_JP": "復旧",
"ko_KR": "복구",
"no_NO": "Gjenopprett",
"pl_PL": "Odzyskaj",
"pt_BR": "Recuperar",
"ru_RU": "Восстановить",
"sv_SE": "Återskapa",
"th_TH": "กู้คืน",
"tr_TR": "Kurtar",
"uk_UA": "Відновити",
"zh_CN": "恢复",
"zh_TW": "復原"
}
},
{
"ID": "RecoverProfile_EmptyList",
"Translations": {
"ar_SA": "لا توجد ملفات شخصية لاستردادها",
"de_DE": "Keine Profile zum Wiederherstellen",
"el_GR": "Δεν υπάρχουν προφίλ για ανάκτηση",
"en_US": "No Profiles To Recover",
"es_ES": "No hay perfiles a recuperar",
"fr_FR": "Aucun profil à restaurer",
"he_IL": "אין פרופילים לשחזור",
"it_IT": "Nessun profilo da recuperare",
"ja_JP": "復元するプロファイルはありません",
"ko_KR": "복구할 프로필 없음",
"no_NO": "Ingen profiler å gjenopprette",
"pl_PL": "Brak profili do odzyskania",
"pt_BR": "Nenhum perfil para recuperar",
"ru_RU": "Нет учётных записей для восстановления",
"sv_SE": "Inga profiler att återskapa",
"th_TH": "ไม่มีโปรไฟล์ที่สามารถกู้คืนได้",
"tr_TR": "Kurtarılacak profil bulunamadı",
"uk_UA": "Немає профілів для відновлення",
"zh_CN": "没有可以恢复的用户数据",
"zh_TW": "無設定檔可復原"
}
},
{
"ID": "ManageSaves_SortByName",
"Translations": {
"ar_SA": "الاسم",
"de_DE": "",
"el_GR": "Όνομα",
"en_US": "Name",
"es_ES": "Nombre",
"fr_FR": "Nom",
"he_IL": "שם",
"it_IT": "Nome",
"ja_JP": "名称",
"ko_KR": "이름",
"no_NO": "Navn",
"pl_PL": "Nazwa",
"pt_BR": "Nome",
"ru_RU": "Название",
"sv_SE": "Namn",
"th_TH": "ชื่อ",
"tr_TR": "İsim",
"uk_UA": "Назва",
"zh_CN": "名称",
"zh_TW": "名稱"
}
},
{
"ID": "ManageSaves_SortBySize",
"Translations": {
"ar_SA": "الحجم",
"de_DE": "Größe",
"el_GR": "Μέγεθος",
"en_US": "Size",
"es_ES": "Tamaño",
"fr_FR": "Taille",
"he_IL": "גודל",
"it_IT": "Dimensione",
"ja_JP": "サイズ",
"ko_KR": "크기",
"no_NO": "Størrelse",
"pl_PL": "Rozmiar",
"pt_BR": "Tamanho",
"ru_RU": "Размер",
"sv_SE": "Storlek",
"th_TH": "ขนาด",
"tr_TR": "Boyut",
"uk_UA": "Розмір",
"zh_CN": "大小",
"zh_TW": "大小"
}
},
{
"ID": "ManageSaves_SortOrderAscending",
"Translations": {
"ar_SA": "تصاعدي",
"de_DE": "Aufsteigend",
"el_GR": "Αύξουσα",
"en_US": "Ascending",
"es_ES": "Ascendente",
"fr_FR": "Croissant",
"he_IL": "סדר עולה",
"it_IT": "Crescente",
"ja_JP": "昇順",
"ko_KR": "오름차순",
"no_NO": "Stigende",
"pl_PL": "Rosnąco",
"pt_BR": "Ascendente",
"ru_RU": "По Возрастанию",
"sv_SE": "Stigande",
"th_TH": "จากน้อยไปมาก",
"tr_TR": "Artan",
"uk_UA": "За зростанням",
"zh_CN": "升序",
"zh_TW": "從小到大"
}
},
{
"ID": "ManageSaves_SortOrderDescending",
"Translations": {
"ar_SA": "تنازلي",
"de_DE": "Absteigend",
"el_GR": "Φθίνουσα",
"en_US": "Descending",
"es_ES": "Descendente",
"fr_FR": "Décroissant",
"he_IL": "סדר יורד",
"it_IT": "Decrescente",
"ja_JP": "降順",
"ko_KR": "내림차순",
"no_NO": "Synkende",
"pl_PL": "Malejąco",
"pt_BR": "Descendente",
"ru_RU": "По Убыванию",
"sv_SE": "Fallande",
"th_TH": "จากมากไปน้อย",
"tr_TR": "Azalan",
"uk_UA": "За спаданням",
"zh_CN": "降序",
"zh_TW": "從大到小"
}
},
{
"ID": "ManageSaves_Search",
"Translations": {
"ar_SA": "بحث",
"de_DE": "Suche",
"el_GR": "Αναζήτηση",
"en_US": "Search",
"es_ES": "Buscar",
"fr_FR": "Rechercher",
"he_IL": "חפש",
"it_IT": "Cerca",
"ja_JP": "検索",
"ko_KR": "찾기",
"no_NO": "Søk",
"pl_PL": "Wyszukaj",
"pt_BR": "Buscar",
"ru_RU": "Поиск",
"sv_SE": "Sök",
"th_TH": "ค้นหา",
"tr_TR": "Ara",
"uk_UA": "Пошук",
"zh_CN": "搜索",
"zh_TW": "搜尋"
}
},
{
"ID": "IrreversibleActionNote",
"Translations": {
"ar_SA": "هذا الإجراء لا يمكن التراجع عنه.",
"de_DE": "Diese Option kann nicht rückgängig gemacht werden.",
"el_GR": "Αυτή η ενέργεια είναι μη αναστρέψιμη.",
"en_US": "This action is not reversible.",
"es_ES": "Esta acción no es reversible.",
"fr_FR": "Cette action n'est pas réversible.",
"he_IL": "הפעולה הזו בלתי הפיכה.",
"it_IT": "Questa azione non è reversibile.",
"ja_JP": "この操作は元に戻せません.",
"ko_KR": "이 작업은 되돌릴 수 없습니다.",
"no_NO": "Denne handlingen er ikke reverserbar.",
"pl_PL": "Ta czynność nie jest odwracalna.",
"pt_BR": "Esta ação não é reversível.",
"ru_RU": "Данное действие является необратимым.",
"sv_SE": "Denna åtgärd går inte att ångra.",
"th_TH": "การดำเนินการนี้ไม่สามารถย้อนกลับได้",
"tr_TR": "Bu eylem geri alınamaz.",
"uk_UA": "Цю дію не можна скасувати.",
"zh_CN": "删除后不可恢复。",
"zh_TW": "此動作將無法復原。"
}
},
{
"ID": "ButtonClose",
"Translations": {
"ar_SA": "إغلاق",
"de_DE": "Schließen",
"el_GR": "Κλείσιμο",
"en_US": "Close",
"es_ES": "Cerrar",
"fr_FR": "Fermer",
"he_IL": "סגירה",
"it_IT": "Chiudi",
"ja_JP": "閉じる",
"ko_KR": "닫기",
"no_NO": "Lukk",
"pl_PL": "Zamknij",
"pt_BR": "Fechar",
"ru_RU": "Закрыть",
"sv_SE": "Stäng",
"th_TH": "ปิด",
"tr_TR": "Kapat",
"uk_UA": "Закрити",
"zh_CN": "关闭",
"zh_TW": "關閉"
}
},
{
"ID": "NameLabel",
"Translations": {
"ar_SA": "الاسم:",
"de_DE": null,
"el_GR": "Όνομα:",
"en_US": "Name:",
"es_ES": "Nombre:",
"fr_FR": "Nom :",
"he_IL": "שם:",
"it_IT": "Nome:",
"ja_JP": "名称:",
"ko_KR": "이름 :",
"no_NO": "Navn:",
"pl_PL": "Nazwa:",
"pt_BR": "Nome:",
"ru_RU": "Имя:",
"sv_SE": "Namn:",
"th_TH": "ชื่อ:",
"tr_TR": "İsim:",
"uk_UA": "Імʼя",
"zh_CN": "名称:",
"zh_TW": "名稱:"
}
},
{
"ID": "ProfileNameSelectionWatermark",
"Translations": {
"ar_SA": "اختر اسم الملف الشخصي",
"de_DE": "Wähle einen Profilnamen",
"el_GR": "Επιλέξτε όνομα προφίλ",
"en_US": "Choose a Profile Name",
"es_ES": "Escoge un Nombre de Perfil",
"fr_FR": "Choisir un Nom de Profil",
"he_IL": "בחרו שם פרופיל",
"it_IT": "Scegli un Nome Profilo",
"ja_JP": "プロフィール名を選択",
"ko_KR": "프로필 이름 선택",
"no_NO": "Velg et Profilnavn",
"pl_PL": "Wybierz nazwę profilu",
"pt_BR": "Escolha um Nome de Perfil",
"ru_RU": "Выберите имя профиля",
"sv_SE": "Välj ett Profilnamn",
"th_TH": "เลือก ชื่อโปรไฟล์",
"tr_TR": "Profil Adı Seç",
"uk_UA": "Оберіть ім'я профілю",
"zh_CN": "选择个人资料名称",
"zh_TW": "選擇個人資料名稱"
}
},
{
"ID": "UserIdLabel",
"Translations": {
"ar_SA": "معرف المستخدم:",
"de_DE": "Benutzer-ID:",
"el_GR": "Ταυτότητα Χρήστη:",
"en_US": "User ID:",
"es_ES": "ID de Usuario:",
"fr_FR": "Identifiant Utilisateur :",
"he_IL": "מזהה משתמש:",
"it_IT": "ID utente:",
"ja_JP": "ユーザID:",
"ko_KR": "사용자 ID :",
"no_NO": "Bruker ID:",
"pl_PL": "ID Użytkownika:",
"pt_BR": "ID de Usuário:",
"ru_RU": "ID пользователя:",
"sv_SE": "Användar-id:",
"th_TH": "รหัสผู้ใช้:",
"tr_TR": "Kullanıcı ID:",
"uk_UA": "ID користувача:",
"zh_CN": "用户 ID",
"zh_TW": "使用者 ID:"
}
},
{
"ID": "ProfileImage_Import",
"Translations": {
"ar_SA": "استيراد الصورة",
"de_DE": "Bild importieren",
"el_GR": "Εισαγωγή Εικόνας",
"en_US": "Import Image",
"es_ES": "Importar Imagen",
"fr_FR": "Importer une image",
"he_IL": "ייבוא תמונה",
"it_IT": "Importa immagine",
"ja_JP": "画像をインポート",
"ko_KR": "이미지 가져오기",
"no_NO": "Importer bilde",
"pl_PL": "Importuj obraz",
"pt_BR": "Importar Imagem",
"ru_RU": "Импорт изображения",
"sv_SE": "Importera bild",
"th_TH": "นำเข้าภาพ",
"tr_TR": "Resim İçeri Aktar",
"uk_UA": "Імпорт зображення",
"zh_CN": "导入图像",
"zh_TW": "匯入圖像"
}
},
{
"ID": "ProfileImage_SelectAvatar",
"Translations": {
"ar_SA": "حدد صورة الأفاتار من البرنامج الثابت",
"de_DE": "Firmware-Avatar auswählen",
"el_GR": "Επιλέξτε Avatar από Firmware",
"en_US": "Select Firmware Avatar",
"es_ES": "Seleccionar Avatar del Firmware",
"fr_FR": "Choisir un Avatar du Firmware",
"he_IL": "בחרו אוואטר קושחה",
"it_IT": "Seleziona avatar dal firmware",
"ja_JP": "ファームウェア内のアバターを選択",
"ko_KR": "펌웨어 아바타 선택",
"no_NO": "Velg firmware-avatar",
"pl_PL": "Wybierz avatar z oprogramowania",
"pt_BR": "Selecionar Avatar do Firmware",
"ru_RU": "Выбрать аватар прошивки",
"sv_SE": "Välj avatar från firmware",
"th_TH": "เลือกอวาต้าจากระบบ",
"tr_TR": "Yazılım Avatarı Seç",
"uk_UA": "Виберіть аватар прошивки",
"zh_CN": "选择固件头像",
"zh_TW": "選取韌體大頭貼"
}
},
{
"ID": "SupportedImageFormatDialogTitle",
"Translations": {
"ar_SA": "اختر إما JPG أو JPEG أو PNG أو BMP",
"de_DE": "Wählen Sie entweder ein JPG, JPEG, PNG oder BMP",
"el_GR": "Επιλέξτε είτε JPG, JPEG, PNG ή BMP",
"en_US": "Choose either a JPG, JPEG, PNG, or BMP",
"es_ES": "Elige ya sea JPG, JPEG, PNG o BMP",
"fr_FR": "Choisissez soit un JPG, JPEG, PNG ou BMP",
"he_IL": "בחר את JPG, JPEG, PNG או BMP",
"it_IT": "Scegli tra JPG, JPEG, PNG o BMP",
"ja_JP": "JPG、JPEG、PNG、またはBMPのいずれかを選択してください",
"ko_KR": "JPG, JPEG, PNG 또는 BMP 중에서 선택하세요",
"no_NO": "Velg enten et JPG, JPEG, PNG eller BMP",
"pl_PL": "Wybierz JPG, JPEG, PNG lub BMP",
"pt_BR": "Escolha JPG, JPEG, PNG ou BMP",
"ru_RU": "Выберите либо JPG, JPEG, PNG, или BMP",
"sv_SE": "Välj antingen ett JPG, JPEG, PNG eller BMP",
"th_TH": "เลือก JPG, JPEG, PNG หรือ BMP",
"tr_TR": "JPG, JPEG, PNG veya BMP seçin",
"uk_UA": "Виберіть або JPG, JPEG, PNG, або BMP",
"zh_CN": "选择 JPG、JPEG、PNG 或 BMP",
"zh_TW": "選擇 JPG、JPEG、PNG 或 BMP"
}
},
{
"ID": "SelectAvatarTitle",
"Translations": {
"ar_SA": "تحديد أفاتار البرنامج الثابت",
"de_DE": "Firmware-Avatar auswählen",
"el_GR": "Επιλογή Avatar Firmware",
"en_US": "Select Firmware Avatar",
"es_ES": "Seleccionar Avatar del Firmware",
"fr_FR": "Sélection dun Avatar du Firmware",
"he_IL": "בחירת אוואטר קושחה",
"it_IT": "Selezione Avatar Firmware",
"ja_JP": "ファームウェアアバター選択",
"ko_KR": "펌웨어 아바타 선택",
"no_NO": "Velg firmware-avatar",
"pl_PL": "Wybór awatara oprogramowania",
"pt_BR": "Selecionar Avatar do Firmware",
"ru_RU": "Выбор аватара прошивки",
"sv_SE": "Välj firmware-avatar",
"th_TH": "การเลือกอวตารเฟิร์มแวร์",
"tr_TR": "Firmware Avatar Seçimi",
"uk_UA": "Вибір аватара прошивки",
"zh_CN": "选择固件头像",
"zh_TW": "選取韌體頭像"
}
},
{
"ID": "ButtonChooseAvatar",
"Translations": {
"ar_SA": "اختر الأفاتار",
"de_DE": "Wähle Avatar",
"el_GR": "Επιλέξτε Avatar",
"en_US": "Choose Avatar",
"es_ES": "Elegir Avatar",
"fr_FR": "Choisir un Avatar",
"he_IL": "בחרו אוואטר",
"it_IT": "Scegli Avatar",
"ja_JP": "アバターを選択",
"ko_KR": "아바타 선택",
"no_NO": "Velg avatar",
"pl_PL": "Wybierz awatar",
"pt_BR": "Escolher Avatar",
"ru_RU": "Выбрать аватар",
"sv_SE": "Välj avatar",
"th_TH": "เลือกอวาต้าของคุณ",
"tr_TR": "Avatar Seç",
"uk_UA": "Вибрати аватар",
"zh_CN": "选择头像",
"zh_TW": "選擇大頭貼"
}
},
{
"ID": "DialogUserProfileUnsavedChangesMessage",
"Translations": {
"ar_SA": "لقد قمت بإجراء تغييرات غير محفوظة على هذا الملف الشخصي.",
"de_DE": "Sie haben nicht gespeicherte Änderungen an diesem Profil.",
"el_GR": "Έχετε μη αποθηκευμένες αλλαγές σε αυτό το προφίλ.",
"en_US": "You have unsaved changes to this profile.",
"es_ES": "Tienes cambios no guardados en este perfil.",
"fr_FR": "Vous avez des modifications non enregistrées sur ce profil.",
"he_IL": "ביצעת שינויים לא שמורים בפרופיל זה.",
"it_IT": "Hai modifiche non salvate su questo profilo.",
"ja_JP": "このプロファイルには保存されていない変更があります.",
"ko_KR": "이 프로필에는 저장되지 않은 변경 사항이 있습니다.",
"no_NO": "Du har usparende endringer på denne profilen.",
"pl_PL": "Masz niezapisane zmiany w tym profilu.",
"pt_BR": "Você tem alterações não salvas neste perfil.",
"ru_RU": "У вас есть несохраненные изменения в этом профиле.",
"sv_SE": "Du har osparade ändringar i den här profilen.",
"th_TH": "คุณมีการเปลี่ยนแปลงที่ยังไม่ได้บันทึกในโปรไฟล์นี้",
"tr_TR": "Bu profilde kaydedilmemiş değişiklikleriniz var.",
"uk_UA": "У вас є незбережені зміни в цьому профілі.",
"zh_CN": "您对该账户有未保存的更改。",
"zh_TW": "您對該使用者設定檔有未儲存的變更。"
}
},
{
"ID": "DialogUserProfileUnsavedChangesSubMessage",
"Translations": {
"ar_SA": "هل تريد تجاهل التغييرات؟",
"de_DE": "Verwerfen Sie die Änderungen?",
"el_GR": "Θέλετε να απορρίψετε τις αλλαγές?",
"en_US": "Discard changes?",
"es_ES": "¿Descartar los cambios?",
"fr_FR": "Annuler les modifications ?",
"he_IL": "האם ברצונך להתעלם מהשינויים?",
"it_IT": "Scartare le modifiche?",
"ja_JP": "変更を破棄しますか?",
"ko_KR": "변경 사항을 취소하시겠습니까?",
"no_NO": "Vil du forkaste endringene?",
"pl_PL": "Czy chcesz odrzucić zmiany?",
"pt_BR": "Deseja descartar as alterações?",
"ru_RU": "Отменить изменения?",
"sv_SE": "Vill du förkasta ändringarna?",
"th_TH": "คุณต้องการทิ้งการเปลี่ยนแปลงหรือไม่?",
"tr_TR": "Değişiklikleri iptal et?",
"uk_UA": "Бажаєте скасувати зміни?",
"zh_CN": "确定要放弃更改吗?",
"zh_TW": "您確定要放棄變更嗎?"
}
},
{
"ID": "DialogUserProfileUnsavedChangesTitle",
"Translations": {
"ar_SA": "تحذير - التغييرات غير محفوظة",
"de_DE": "WARNUNG - Nicht gespeicherte Änderungen",
"el_GR": "ΠΡΟΣΟΧΗ - Μην Αποθηκευμένες Αλλαγές.",
"en_US": "WARNING - Unsaved Changes",
"es_ES": "ADVERTENCIA - Cambios Sin Guardar",
"fr_FR": "AVERTISSEMENT - Modifications Non Enregistrées",
"he_IL": "אזהרה - שינויים לא שמורים",
"it_IT": "ATTENZIONE - Modifiche non salvate",
"ja_JP": "警告 - 保存されていない変更",
"ko_KR": "경고 - 저장되지 않은 변경 사항",
"no_NO": "ADVARSEL - Ulagrede endringer",
"pl_PL": "UWAGA - Niezapisane zmiany",
"pt_BR": "ALERTA - Alterações não salvas",
"ru_RU": "ВНИМАНИЕ - Несохраненные изменения",
"sv_SE": "VARNING - Ej sparade ändringar",
"th_TH": "คำเตือน - มีการเปลี่ยนแปลงที่ไม่ได้บันทึก",
"tr_TR": "UYARI - Kaydedilmemiş Değişiklikler",
"uk_UA": "УВАГА — Незбережені зміни",
"zh_CN": "警告 - 有未保存的更改",
"zh_TW": "警告 - 未儲存的變更"
}
},
{
"ID": "ButtonDelete",
"Translations": {
"ar_SA": "حذف",
"de_DE": "Löschen",
"el_GR": "Διαγράφω",
"en_US": "Delete",
"es_ES": "Eliminar",
"fr_FR": "Supprimer",
"he_IL": "מחיקה",
"it_IT": "Elimina",
"ja_JP": "削除",
"ko_KR": "삭제",
"no_NO": "Slett",
"pl_PL": "Usuń",
"pt_BR": "Apagar",
"ru_RU": "Удалить",
"sv_SE": "Ta bort",
"th_TH": "ลบ",
"tr_TR": "Sil",
"uk_UA": "Видалити",
"zh_CN": "删除",
"zh_TW": "刪除"
}
},
{
"ID": "ButtonSave",
"Translations": {
"ar_SA": "حفظ",
"de_DE": "Speichern",
"el_GR": "Αποθήκευση",
"en_US": "Save",
"es_ES": "Guardar",
"fr_FR": "Enregistrer",
"he_IL": "שמור",
"it_IT": "Salva",
"ja_JP": "セーブ",
"ko_KR": "저장",
"no_NO": "Lagre",
"pl_PL": "Zapisz",
"pt_BR": "Salvar",
"ru_RU": "Сохранить",
"sv_SE": "Spara",
"th_TH": "บันทึก",
"tr_TR": "Kaydet",
"uk_UA": "Зберегти",
"zh_CN": "保存",
"zh_TW": "儲存"
}
},
{
"ID": "UserEditorTitle",
"Translations": {
"ar_SA": "جارٍ تعديل {0}",
"de_DE": "{0} wird bearbeitet",
"el_GR": "Επεξεργασία {0}",
"en_US": "Editing {0}",
"es_ES": "Editando {0}",
"fr_FR": "Modification de {0}",
"he_IL": "עריכת {0}",
"it_IT": "Modifica di {0}",
"ja_JP": "{0} を編集中",
"ko_KR": "{0} 편집 중",
"no_NO": "Redigerer {0}",
"pl_PL": "Edycja {0}",
"pt_BR": "Editando {0}",
"ru_RU": "Редактирование {0}",
"sv_SE": "Redigerar {0}",
"th_TH": "กำลังกำลังแก้ไข {0}",
"tr_TR": "{0} düzenleniyor",
"uk_UA": "Редагування {0}",
"zh_CN": "正在编辑 {0}",
"zh_TW": "正在編輯 {0}"
}
},
{
"ID": "UserEditorTitleNewUser",
"Translations": {
"ar_SA": "مستخدم جديد",
"de_DE": "Neuer Nutzer",
"el_GR": "Νέος Χρήστης",
"en_US": "New User",
"es_ES": "Nuevo Usuario",
"fr_FR": "Nouvel Utilisateur",
"he_IL": "משתמש חדש",
"it_IT": "Nuovo utente",
"ja_JP": "新しいユーザー",
"ko_KR": "새 사용자",
"no_NO": "Ny bruker",
"pl_PL": "Nowy użytkownik",
"pt_BR": "Novo usuário",
"ru_RU": "Новый пользователь",
"sv_SE": "Ny användare",
"th_TH": "ผู้ใช้ใหม่",
"tr_TR": "Yeni kullanıcı",
"uk_UA": "Новий користувач",
"zh_CN": "新用户",
"zh_TW": "新使用者"
}
},
{
"ID": "EmptyNameError",
"Translations": {
"ar_SA": "الاسم مطلوب",
"de_DE": "Name ist erforderlich",
"el_GR": "Απαιτείται όνομα",
"en_US": "Name is required",
"es_ES": "El nombre es obligatorio",
"fr_FR": "Le nom est requis",
"he_IL": "נדרש שם",
"it_IT": "Il nome è obbligatorio",
"ja_JP": "名称が必要です",
"ko_KR": "이름 필수 입력",
"no_NO": "Navn er påkrevd",
"pl_PL": "Nazwa jest wymagana",
"pt_BR": "Nome é obrigatório",
"ru_RU": "Необходимо ввести имя",
"sv_SE": "Namn krävs",
"th_TH": "จำเป็นต้องระบุชื่อ",
"tr_TR": "İsim gerekli",
"uk_UA": "Імʼя обовʼязкове",
"zh_CN": "必须输入名称",
"zh_TW": "名稱為必填"
}
}
]
}

View file

@ -16,41 +16,31 @@
<Design.DataContext>
<viewModels:ProfileSelectorDialogViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*,Auto">
<Border
CornerRadius="5"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1">
<Border BorderThickness="0" Padding="-3">
<ListBox
MaxHeight="300"
HorizontalAlignment="Stretch"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="Transparent"
ItemsSource="{Binding Profiles}"
SelectionChanged="ProfilesList_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel
HorizontalAlignment="Left"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal" />
Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Margin" Value="5 5 0 5" />
<Setter Property="Margin" Value="0" />
<Setter Property="CornerRadius" Value="5" />
</Style>
<Style Selector="Rectangle#SelectionIndicator">
<Setter Property="Opacity" Value="0" />
</Style>
</ListBox.Styles>
<ListBox.DataTemplates>
<DataTemplate
DataType="models:UserProfile">
@ -58,6 +48,7 @@
PointerEntered="Grid_PointerEntered"
PointerExited="Grid_OnPointerExited">
<Border
Margin="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
@ -69,37 +60,26 @@
<Image
Width="96"
Height="96"
Margin="0,0,0,10"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Source="{Binding Image, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
<TextBlock
HorizontalAlignment="Stretch"
MaxWidth="90"
Text="{Binding Name}"
Height="30"
MaxWidth="90"
TextAlignment="Center"
TextWrapping="Wrap"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"
MaxLines="2"
Margin="5" />
TextWrapping="Wrap"
MaxLines="2" />
</StackPanel>
</Border>
</Grid>
</DataTemplate>
<DataTemplate
DataType="viewModels:BaseModel">
<Panel
Height="118"
Width="96">
<Panel.Styles>
<Style Selector="Panel">
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}" />
</Style>
</Panel.Styles>
</Panel>
</DataTemplate>
</ListBox.DataTemplates>
</ListBox>
</Border>
</Grid>
</UserControl>

View file

@ -94,7 +94,7 @@ namespace Ryujinx.Ava.UI.Applet
ContentDialog contentDialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
Title = LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle],
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.Cancel],

View file

@ -13,7 +13,7 @@
<MenuItem
Command="{Binding ToggleFavorite}"
CommandParameter="{Binding}"
Header="{ext:Locale GameListContextMenuToggleFavorite}"
Header="{Binding FavoriteStatusText}"
Icon="{ext:Icon fa-solid fa-star}" />
<MenuItem
Command="{Binding CreateApplicationShortcut}"

View file

@ -71,7 +71,7 @@ namespace Ryujinx.Ava.UI.Controls
NavigationDialogHost content = new(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
ContentDialog contentDialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
Title = LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle],
PrimaryButtonText = string.Empty,
SecondaryButtonText = string.Empty,
CloseButtonText = string.Empty,
@ -166,7 +166,7 @@ namespace Ryujinx.Ava.UI.Controls
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionConfirmMessage],
string.Empty,
LocaleManager.Instance[LocaleKeys.IrreversibleActionNote],
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
string.Empty);

View file

@ -10,6 +10,9 @@ namespace Ryujinx.Ava.UI.Models
[ObservableProperty]
public partial byte[] Image { get; set; }
[ObservableProperty]
public partial bool FirmwareFound { get; set; }
[ObservableProperty]
public partial string Name { get; set; } = string.Empty;

View file

@ -2117,6 +2117,8 @@ namespace Ryujinx.Ava.UI.ViewModels
}
);
public string FavoriteStatusText => SelectedApplication?.Favorite == false ? LocaleManager.Instance[LocaleKeys.GameListContextMenuAddToFavorites] : LocaleManager.Instance[LocaleKeys.GameListContextMenuRemoveFromFavorites];
public static RelayCommand<MainWindowViewModel> CreateApplicationShortcut { get; } =
Commands.CreateConditional<MainWindowViewModel>(vm => vm?.SelectedApplication != null,
viewModel => ShortcutHelper.CreateAppShortcut(

View file

@ -28,6 +28,9 @@ namespace Ryujinx.Ava.UI.ViewModels
[ObservableProperty]
public partial ObservableCollection<ProfileImageModel> Images { get; set; }
[ObservableProperty]
public partial bool FirmwareFound { get; set; }
[ObservableProperty]
public partial Color BackgroundColor { get; set; } = Colors.White;
@ -43,17 +46,15 @@ namespace Ryujinx.Ava.UI.ViewModels
};
}
private int _selectedIndex = -1;
public int SelectedIndex
{
get;
get => _selectedIndex;
set
{
field = value;
SelectedImage = field == -1
? null
: Images[field].Data;
_selectedIndex = value;
SelectedImage = value == -1 ? null : Images[value].Data;
OnPropertyChanged();
OnPropertyChanged(nameof(SelectedImage));
}

View file

@ -1,10 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Ryujinx.Ava.UI.ViewModels
{
public partial class UserProfileImageSelectorViewModel : BaseModel
{
[ObservableProperty]
public partial bool FirmwareFound { get; set; }
}
}

View file

@ -1,7 +1,7 @@
using Ryujinx.Ava.UI.Models;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Collections.Specialized;
namespace Ryujinx.Ava.UI.ViewModels
{
@ -9,20 +9,35 @@ namespace Ryujinx.Ava.UI.ViewModels
{
public UserProfileViewModel()
{
Profiles = [];
LostProfiles = [];
IsEmpty = !LostProfiles.Any();
Profiles = new ObservableCollection<BaseModel>();
LostProfiles = new ObservableCollection<UserProfile>();
LostProfiles.CollectionChanged += LostProfilesChanged;
}
public ObservableCollection<BaseModel> Profiles { get; set; }
public ObservableCollection<BaseModel> Profiles { get; }
public ObservableCollection<UserProfile> LostProfiles { get; set; }
public ObservableCollection<UserProfile> LostProfiles { get; }
public bool IsEmpty { get; set; }
public bool IsEmpty => LostProfiles.Count == 0;
public void Dispose()
{
GC.SuppressFinalize(this);
LostProfiles.CollectionChanged -= LostProfilesChanged;
}
private void LostProfilesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(nameof(IsEmpty));
}
public void UpdateLostProfiles(ObservableCollection<UserProfile> newProfiles)
{
LostProfiles.Clear();
foreach (var profile in newProfiles)
LostProfiles.Add(profile);
OnPropertyChanged(nameof(IsEmpty));
}
}
}

View file

@ -27,7 +27,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private readonly AccountManager _accountManager;
public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId);
public string SaveManagerTitle => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UserProfiles_SaveManagerTitle, _accountManager.LastOpenedUser.Name);
public UserSaveManagerViewModel(AccountManager accountManager)
{

View file

@ -27,7 +27,7 @@ namespace Ryujinx.Ava.UI.Views.Dialog
{
PrimaryButtonText = string.Empty,
SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
Content = new AboutView { ViewModel = viewModel }
};

View file

@ -136,7 +136,7 @@
<MenuItem
Command="{Binding ManageProfiles}"
Padding="0"
Header="{ext:Locale MenuBarOptionsManageUserProfiles}"
Header="{ext:Locale UserProfiles_MenuBarOptions_OpenUserProfiles}"
Icon="{ext:Icon fa-solid fa-user}"
IsEnabled="{Binding EnableNonGameRunningControls}"
Classes="withCheckbox">

View file

@ -8,107 +8,102 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
Margin="0"
MinWidth="500"
Padding="0"
mc:Ignorable="d"
Focusable="True"
x:DataType="models:TempProfile">
<Grid Margin="0" ColumnDefinitions="Auto,*" RowDefinitions="*,Auto">
<StackPanel
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Text="{ext:Locale UserProfilesName}" />
<Grid Margin="0,10,0,0" ColumnDefinitions="Auto,*" RowDefinitions="*,Auto">
<StackPanel Spacing="10">
<TextBlock Text="{ext:Locale UserProfiles_NameLabel}" />
<TextBox
Name="NameBox"
Margin="0,0,0,5"
Width="300"
HorizontalAlignment="Stretch"
MaxLength="{Binding MaxProfileNameLength}"
Watermark="{ext:Locale ProfileNameSelectionWatermark}"
Watermark="{ext:Locale UserProfiles_ProfileNameSelectionWatermark}"
Text="{Binding Name}" />
<TextBlock Name="IdText" Text="{ext:Locale UserProfilesUserId}" />
<TextBlock Name="IdText" Text="{ext:Locale UserProfiles_UserIdLabel}" />
<TextBox
Name="IdLabel"
Width="300"
HorizontalAlignment="Stretch"
IsReadOnly="True"
Text="{Binding UserIdString}" />
</StackPanel>
<StackPanel
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Orientation="Vertical">
<Grid Grid.Column="1">
<Border
Name="ImageBox"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1">
BorderThickness="1"
VerticalAlignment="Bottom"
HorizontalAlignment="Right">
<Panel>
<ui:SymbolIcon
FontSize="60"
Width="96"
Height="96"
Margin="0"
FontSize="70"
Width="120"
Height="120"
Foreground="{DynamicResource AppListHoverBackgroundColor}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Symbol="Camera" />
<Image
Name="ProfileImage"
Width="96"
Height="96"
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Width="120"
Height="120"
Source="{Binding Image, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
<Border
Margin="2"
Height="27"
Width="27"
CornerRadius="17"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Background="{DynamicResource ThemeContentBackgroundColor}">
<Button
Name="ProfileImageButton"
MaxHeight="27"
MaxWidth="27"
MinWidth="27"
MinHeight="27"
CornerRadius="17"
Padding="0">
<ui:SymbolIcon Symbol="Edit" />
<Button.Flyout>
<MenuFlyout Placement="Bottom">
<MenuItem
Header="{ext:Locale UserProfiles_ProfileImage_Import}"
Icon="{ext:Icon fa-solid fa-image}"
Click="Import_OnClick" />
<MenuItem
Header="{ext:Locale UserProfiles_ProfileImage_SelectAvatar}"
Icon="{ext:Icon fa-solid fa-floppy-disk}"
Click="SelectFirmwareImage_OnClick" />
</MenuFlyout>
</Button.Flyout>
</Button>
</Border>
</Panel>
</Border>
</StackPanel>
</Grid>
<StackPanel
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Left"
Orientation="Horizontal"
Margin="0 24 0 0"
Margin="0,30,0,0"
Spacing="10">
<Button
Width="50"
MinWidth="50"
Click="BackButton_Click">
<Button MinWidth="50" Width="50" Click="BackButton_Click">
<ui:SymbolIcon Symbol="Back" />
</Button>
</StackPanel>
<StackPanel
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Right"
Orientation="Horizontal"
Margin="0 24 0 0"
Margin="0,30,0,0"
Spacing="10">
<Button
Name="DeleteButton"
Click="DeleteButton_Click">
<TextBlock Text="{ext:Locale UserProfilesDelete}" />
<Button Name="DeleteButton" Click="DeleteButton_Click">
<TextBlock Text="{ext:Locale UserProfiles_ButtonDelete}" />
</Button>
<Button
Name="ChangePictureButton"
Click="ChangePictureButton_Click">
<TextBlock Text="{ext:Locale UserProfilesChangeProfileImage}" />
</Button>
<Button
Name="AddPictureButton"
Click="ChangePictureButton_Click">
<TextBlock Text="{ext:Locale UserProfilesSetProfileImage}" />
</Button>
<Button
Name="SaveButton"
Click="SaveButton_Click">
<TextBlock Text="{ext:Locale UserProfilesSetProfileImage}" />
<Button Name="SaveButton" Click="SaveButton_Click">
<TextBlock Text="{ext:Locale UserProfiles_ButtonSave}" />
</Button>
</StackPanel>
</Grid>

View file

@ -1,6 +1,10 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Media;
using Avalonia.Platform.Storage;
using Avalonia.VisualTree;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Common.Locale;
@ -8,6 +12,10 @@ using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.FileSystem;
using SkiaSharp;
using System.Collections.Generic;
using System.IO;
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
namespace Ryujinx.Ava.UI.Views.User
@ -15,90 +23,75 @@ namespace Ryujinx.Ava.UI.Views.User
public partial class UserEditorView : RyujinxControl<TempProfile>
{
private NavigationDialogHost _parent;
private ContentManager _contentManager;
private UserProfile _profile;
private TempProfile _tempProfile;
private bool _isNewUser;
public static uint MaxProfileNameLength => 0x20;
public bool IsDeletable => _profile.UserId != AccountManager.DefaultUserId;
public string UserEditorTitle => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UserProfiles_UserEditorTitle, _profile.Name);
public UserEditorView()
{
InitializeComponent();
AddHandler(Frame.NavigatedToEvent, (s, e) =>
{
NavigatedTo(e);
}, RoutingStrategies.Direct);
AddHandler(Frame.NavigatedToEvent, (s, e) => NavigatedTo(e), RoutingStrategies.Direct);
}
private void NavigatedTo(NavigationEventArgs arg)
{
if (Program.PreviewerDetached)
if (!Program.PreviewerDetached)
return;
if (arg.NavigationMode == NavigationMode.New)
{
switch (arg.NavigationMode)
{
case NavigationMode.New:
(NavigationDialogHost parent, UserProfile profile, bool isNewUser) = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
_isNewUser = isNewUser;
_profile = profile;
ViewModel = new TempProfile(_profile);
(NavigationDialogHost parent, UserProfile profile, bool isNewUser) =
((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
_parent = parent;
break;
}
_parent = parent;
_profile = profile;
_isNewUser = isNewUser;
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " +
$"{(_isNewUser ? LocaleManager.Instance[LocaleKeys.UserEditorTitleCreate] : LocaleManager.Instance[LocaleKeys.UserEditorTitle])}";
DataValidationErrors.ClearErrors(NameBox);
DataValidationErrors.ClearErrors(ImageBox);
ImageBox.Bind(Border.BorderBrushProperty, new DynamicResourceExtension("AppListHoverBackgroundColor"));
AddPictureButton.IsVisible = _isNewUser;
ChangePictureButton.IsVisible = !_isNewUser;
IdLabel.IsVisible = _profile != null;
IdText.IsVisible = _profile != null;
if (!_isNewUser && IsDeletable)
{
DeleteButton.IsVisible = true;
}
else
{
DeleteButton.IsVisible = false;
}
ViewModel = new TempProfile(_profile);
_tempProfile = ViewModel;
_contentManager = _parent.ContentManager;
ViewModel.FirmwareFound = _contentManager.GetCurrentFirmwareVersion() != null;
}
((ContentDialog)_parent.Parent).Title =
$"{LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle]} - " +
$"{(_isNewUser ? LocaleManager.Instance[LocaleKeys.UserProfiles_UserEditorTitleNewUser] : UserEditorTitle)}";
bool hasProfile = _profile != null;
IdLabel.IsVisible = hasProfile;
IdText.IsVisible = hasProfile;
DeleteButton.IsVisible = !_isNewUser && IsDeletable;
}
private async void BackButton_Click(object sender, RoutedEventArgs e)
{
if (_isNewUser)
bool hasUnsavedChanges =
_isNewUser
? (ViewModel.Name != string.Empty || ViewModel.Image != null)
: (_profile.Name != ViewModel.Name || _profile.Image != ViewModel.Image);
if (hasUnsavedChanges)
{
if (ViewModel.Name != string.Empty || ViewModel.Image != null)
{
if (await ContentDialogHelper.CreateChoiceDialog(
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage],
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage]))
{
_parent?.GoBack();
}
}
else
{
bool confirm = await ContentDialogHelper.CreateChoiceDialog(
LocaleManager.Instance[LocaleKeys.UserProfiles_DialogUserProfileUnsavedChangesTitle],
LocaleManager.Instance[LocaleKeys.UserProfiles_DialogUserProfileUnsavedChangesMessage],
LocaleManager.Instance[LocaleKeys.UserProfiles_DialogUserProfileUnsavedChangesSubMessage]);
if (confirm)
_parent?.GoBack();
}
}
else
{
if (_profile.Name != ViewModel.Name || _profile.Image != ViewModel.Image)
{
if (await ContentDialogHelper.CreateChoiceDialog(
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage],
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage]))
{
_parent?.GoBack();
}
}
else
{
_parent?.GoBack();
}
_parent?.GoBack();
}
}
@ -110,18 +103,28 @@ namespace Ryujinx.Ava.UI.Views.User
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
DataValidationErrors.ClearErrors(NameBox);
DataValidationErrors.ClearErrors(ImageBox);
ImageBox.Bind(Border.BorderBrushProperty, new DynamicResourceExtension("AppListHoverBackgroundColor"));
if (string.IsNullOrWhiteSpace(ViewModel.Name))
bool nameEmpty = string.IsNullOrWhiteSpace(ViewModel.Name);
bool imageMissing = ViewModel.Image == null;
if (nameEmpty && imageMissing)
{
DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfiles_EmptyNameError]));
DataValidationErrors.SetError(ImageBox, new DataValidationException(""));
ImageBox.BorderBrush = Brush.Parse("#ff99a4");
return;
}
else if (nameEmpty)
{
DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfileEmptyNameError]));
DataValidationErrors.SetError(NameBox,new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfiles_EmptyNameError]));
return;
}
if (ViewModel.Image == null)
else if (imageMissing)
{
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, ViewModel));
DataValidationErrors.SetError(ImageBox, new DataValidationException(""));
ImageBox.BorderBrush = Brush.Parse("#ff99a4");
return;
}
@ -130,6 +133,7 @@ namespace Ryujinx.Ava.UI.Views.User
_profile.Name = ViewModel.Name;
_profile.Image = ViewModel.Image;
_profile.UpdateState();
_parent.AccountManager.SetUserName(_profile.UserId, _profile.Name);
_parent.AccountManager.SetUserImage(_profile.UserId, _profile.Image);
}
@ -145,17 +149,56 @@ namespace Ryujinx.Ava.UI.Views.User
_parent?.GoBack();
}
public void SelectProfileImage()
private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
{
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, ViewModel));
}
private void ChangePictureButton_Click(object sender, RoutedEventArgs e)
{
if (_profile != null || _isNewUser)
if (ViewModel.FirmwareFound)
{
SelectProfileImage();
_parent.Navigate(typeof(UserFirmwareAvatarSelectorView), (_parent, _tempProfile));
}
}
private async void Import_OnClick(object sender, RoutedEventArgs e)
{
var window = (Window)this.GetVisualRoot()!;
var result = await window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.UserProfiles_SupportedImageFormatDialogTitle],
AllowMultiple = false,
FileTypeFilter = new List<FilePickerFileType>
{
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
{
Patterns = ["*.jpg", "*.jpeg", "*.png", "*.bmp"],
AppleUniformTypeIdentifiers = ["public.jpeg", "public.png", "com.microsoft.bmp"],
MimeTypes = ["image/jpeg", "image/png", "image/bmp"],
},
},
});
if (result.Count == 0 || DataContext is not TempProfile temp)
return;
temp.Image = ProcessProfileImage(File.ReadAllBytes(result[0].Path.LocalPath));
if (_profile != null)
_profile.Image = temp.Image;
}
private static byte[] ProcessProfileImage(byte[] buffer)
{
using SKBitmap bitmap = SKBitmap.Decode(buffer);
SKBitmap resizedBitmap = bitmap.Resize(new SKImageInfo(256, 256), SKFilterQuality.High);
using MemoryStream streamJpg = new();
if (resizedBitmap != null)
{
using SKImage image = SKImage.FromBitmap(resizedBitmap);
using SKData dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100);
dataJpeg.SaveTo(streamJpg);
}
return streamJpg.ToArray();
}
}
}
}

View file

@ -17,57 +17,65 @@
<Design.DataContext>
<viewModels:UserFirmwareAvatarSelectorViewModel />
</Design.DataContext>
<Grid
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto,Auto">
<ListBox
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto,Auto">
<Border
Grid.Row="1"
BorderThickness="0"
SelectedIndex="{Binding SelectedIndex}"
Height="400"
ItemsSource="{Binding Images}"
Padding="2.5"
BorderThickness="1"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
CornerRadius="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel
Orientation="Horizontal"
Margin="0"
HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Width" Value="85" />
<Setter Property="MaxWidth" Value="85" />
<Setter Property="MinWidth" Value="85" />
</Style>
<Style Selector="ListBoxItem /template/ Rectangle#SelectionIndicator">
<Setter Property="MinHeight" Value="70" />
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate>
<Panel
Background="{Binding BackgroundColor}"
Margin="5">
<Image Source="{Binding Data, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
</Panel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
VerticalAlignment="Stretch">
<ListBox
Grid.Row="1"
Background="Transparent"
SelectedIndex="{Binding SelectedIndex}"
Height="400"
ItemsSource="{Binding Images}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel
Orientation="Horizontal"
Margin="0"
HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Width" Value="85" />
<Setter Property="MaxWidth" Value="85" />
<Setter Property="MinWidth" Value="85" />
</Style>
<Style Selector="ListBoxItem /template/ Rectangle#SelectionIndicator">
<Setter Property="MinHeight" Value="70" />
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
<Setter Property="IsVisible" Value="True" />
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate>
<Panel
Background="{Binding BackgroundColor}"
Margin="5">
<Image Source="{Binding Data, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
</Panel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
<StackPanel
Grid.Row="3"
Orientation="Horizontal"
Spacing="10"
Margin="0 24 0 0"
Margin="0,30,0,0"
HorizontalAlignment="Left">
<Button
Width="50"
MinWidth="50"
Height="35"
Height="37"
Click="GoBack">
<ui:SymbolIcon Symbol="Back" />
</Button>
@ -76,7 +84,7 @@
Grid.Row="3"
Orientation="Horizontal"
Spacing="10"
Margin="0 24 0 0"
Margin="0,30,0,0"
HorizontalAlignment="Right">
<ui:ColorPickerButton
FlyoutPlacement="Top"
@ -95,10 +103,10 @@
</ui:ColorPickerButton.Styles>
</ui:ColorPickerButton>
<Button
Content="{ext:Locale AvatarChoose}"
Height="35"
Name="ChooseButton"
Click="ChooseButton_OnClick" />
Height="37"
Click="ChooseButton_OnClick">
<TextBlock Text="{ext:Locale UserProfiles_ButtonChooseAvatar}" />
</Button>
</StackPanel>
</Grid>
</UserControl>

View file

@ -1,12 +1,15 @@
using Avalonia.Interactivity;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
using SkiaSharp;
using System.IO;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.User
{
@ -14,44 +17,53 @@ namespace Ryujinx.Ava.UI.Views.User
{
private NavigationDialogHost _parent;
private TempProfile _profile;
public ContentManager ContentManager { get; private set; }
public UserFirmwareAvatarSelectorView()
{
InitializeComponent();
ViewModel = new UserFirmwareAvatarSelectorViewModel();
DataContext = ViewModel;
AddHandler(Frame.NavigatedToEvent, (s, e) => NavigatedTo(e), RoutingStrategies.Direct);
}
public UserFirmwareAvatarSelectorView(ContentManager contentManager)
{
ContentManager = contentManager;
InitializeComponent();
}
public UserFirmwareAvatarSelectorView()
{
InitializeComponent();
AddHandler(Frame.NavigatedToEvent, (s, e) =>
{
NavigatedTo(e);
}, RoutingStrategies.Direct);
ViewModel = new UserFirmwareAvatarSelectorViewModel();
DataContext = ViewModel;
AddHandler(Frame.NavigatedToEvent, (s, e) => NavigatedTo(e), RoutingStrategies.Direct);
}
private void NavigatedTo(NavigationEventArgs arg)
{
if (Program.PreviewerDetached)
if (!Program.PreviewerDetached)
return;
if (arg.NavigationMode != NavigationMode.New)
return;
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
ContentManager = _parent.ContentManager;
((ContentDialog)_parent.Parent).Title =
$"{LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle]} - " +
$"{LocaleManager.Instance[LocaleKeys.UserProfiles_SelectAvatarTitle]}";
ViewModel.SelectedIndex = -1;
_ = Task.Run(() =>
{
if (arg.NavigationMode == NavigationMode.New)
bool found = ContentManager.GetCurrentFirmwareVersion() != null;
Dispatcher.UIThread.Post(() =>
{
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
ContentManager = _parent.ContentManager;
if (Program.PreviewerDetached)
{
ViewModel = new UserFirmwareAvatarSelectorViewModel();
}
DataContext = ViewModel;
}
}
ViewModel.FirmwareFound = found;
});
});
}
public ContentManager ContentManager { get; private set; }
private void GoBack(object sender, RoutedEventArgs e)
{
_parent.GoBack();
@ -59,32 +71,31 @@ namespace Ryujinx.Ava.UI.Views.User
private void ChooseButton_OnClick(object sender, RoutedEventArgs e)
{
if (ViewModel.SelectedImage != null)
if (ViewModel.SelectedImage == null)
return;
using MemoryStream streamJpg = new();
using SKBitmap bitmap = SKBitmap.Decode(ViewModel.SelectedImage);
using SKBitmap newBitmap = new(bitmap.Width, bitmap.Height);
using (SKCanvas canvas = new(newBitmap))
{
using MemoryStream streamJpg = new();
using SKBitmap bitmap = SKBitmap.Decode(ViewModel.SelectedImage);
using SKBitmap newBitmap = new(bitmap.Width, bitmap.Height);
using (SKCanvas canvas = new(newBitmap))
{
canvas.Clear(new SKColor(
ViewModel.BackgroundColor.R,
ViewModel.BackgroundColor.G,
ViewModel.BackgroundColor.B,
ViewModel.BackgroundColor.A));
canvas.DrawBitmap(bitmap, 0, 0);
}
using (SKImage image = SKImage.FromBitmap(newBitmap))
using (SKData dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100))
{
dataJpeg.SaveTo(streamJpg);
}
_profile.Image = streamJpg.ToArray();
_parent.GoBack();
canvas.Clear(new SKColor(
ViewModel.BackgroundColor.R,
ViewModel.BackgroundColor.G,
ViewModel.BackgroundColor.B,
ViewModel.BackgroundColor.A));
canvas.DrawBitmap(bitmap, 0, 0);
}
using (SKImage image = SKImage.FromBitmap(newBitmap))
using (SKData dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100))
{
dataJpeg.SaveTo(streamJpg);
}
_profile.Image = streamJpg.ToArray();
_parent.GoBack();
}
}
}
}

View file

@ -1,57 +0,0 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:viewModles="clr-namespace:Ryujinx.Ava.UI.ViewModels"
Focusable="True"
mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Views.User.UserProfileImageSelectorView"
x:DataType="viewModles:UserProfileImageSelectorViewModel"
Width="500"
d:DesignWidth="500">
<Design.DataContext>
<viewModles:UserProfileImageSelectorViewModel />
</Design.DataContext>
<Grid
HorizontalAlignment="Stretch"
VerticalAlignment="Center" RowDefinitions="Auto,70,Auto">
<TextBlock
Grid.Row="0"
TextWrapping="Wrap"
HorizontalAlignment="Left"
TextAlignment="Start"
Text="{ext:Locale ProfileImageSelectionNote}" />
<StackPanel
Grid.Row="2"
Spacing="10"
HorizontalAlignment="Left"
Orientation="Horizontal">
<Button
Width="50"
MinWidth="50"
Click="GoBack">
<ui:SymbolIcon Symbol="Back" />
</Button>
</StackPanel>
<StackPanel
Grid.Row="2"
Spacing="10"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
Name="Import"
Click="Import_OnClick">
<TextBlock Text="{ext:Locale ProfileImageSelectionImportImage}" />
</Button>
<Button
Name="SelectFirmwareImage"
IsEnabled="{Binding FirmwareFound}"
Click="SelectFirmwareImage_OnClick">
<TextBlock Text="{ext:Locale ProfileImageSelectionSelectAvatar}" />
</Button>
</StackPanel>
</Grid>
</UserControl>

View file

@ -1,118 +0,0 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Avalonia.VisualTree;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
using SkiaSharp;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.Ava.UI.Views.User
{
public partial class UserProfileImageSelectorView : RyujinxControl<UserProfileImageSelectorViewModel>
{
private ContentManager _contentManager;
private NavigationDialogHost _parent;
private TempProfile _profile;
public UserProfileImageSelectorView()
{
InitializeComponent();
AddHandler(Frame.NavigatedToEvent, (s, e) =>
{
NavigatedTo(e);
}, RoutingStrategies.Direct);
}
private void NavigatedTo(NavigationEventArgs arg)
{
if (Program.PreviewerDetached)
{
switch (arg.NavigationMode)
{
case NavigationMode.New:
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
_contentManager = _parent.ContentManager;
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.ProfileImageSelectionHeader]}";
if (Program.PreviewerDetached)
{
DataContext = ViewModel = new UserProfileImageSelectorViewModel();
ViewModel.FirmwareFound = _contentManager.GetCurrentFirmwareVersion() != null;
}
break;
case NavigationMode.Back:
if (_profile.Image != null)
{
_parent.GoBack();
}
break;
}
}
}
private async void Import_OnClick(object sender, RoutedEventArgs e)
{
IReadOnlyList<IStorageFile> result = await ((Window)this.GetVisualRoot()!).StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
AllowMultiple = false,
FileTypeFilter = new List<FilePickerFileType>
{
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
{
Patterns = ["*.jpg", "*.jpeg", "*.png", "*.bmp"],
AppleUniformTypeIdentifiers = ["public.jpeg", "public.png", "com.microsoft.bmp"],
MimeTypes = ["image/jpeg", "image/png", "image/bmp"],
},
},
});
if (result.Count > 0)
{
_profile.Image = ProcessProfileImage(File.ReadAllBytes(result[0].Path.LocalPath));
_parent.GoBack();
}
}
private void GoBack(object sender, RoutedEventArgs e)
{
_parent.GoBack();
}
private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
{
if (ViewModel.FirmwareFound)
{
_parent.Navigate(typeof(UserFirmwareAvatarSelectorView), (_parent, _profile));
}
}
private static byte[] ProcessProfileImage(byte[] buffer)
{
using SKBitmap bitmap = SKBitmap.Decode(buffer);
SKBitmap resizedBitmap = bitmap.Resize(new SKImageInfo(256, 256), SKFilterQuality.High);
using MemoryStream streamJpg = new();
if (resizedBitmap != null)
{
using SKImage image = SKImage.FromBitmap(resizedBitmap);
using SKData dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100);
dataJpeg.SaveTo(streamJpg);
}
return streamJpg.ToArray();
}
}
}

View file

@ -17,37 +17,48 @@
<Design.DataContext>
<viewModels:UserProfileViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" RowDefinitions="*,Auto">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*,Auto">
<Border
CornerRadius="5"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1"
Grid.Row="0">
Grid.Row="0"
Padding="2.5">
<Panel>
<ListBox
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding LostProfiles}">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="10" />
<Setter Property="Margin" Value="0" />
</Style>
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
<Setter Property="IsVisible" Value="False" />
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate>
<Border
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
CornerRadius="5">
CornerRadius="4">
<Grid Margin="0" ColumnDefinitions="*,Auto">
<TextBlock
HorizontalAlignment="Stretch"
Margin="5"
Text="{Binding UserId}"
TextAlignment="Start"
TextWrapping="Wrap" />
<Button Grid.Column="1"
HorizontalAlignment="Right"
Click="Recover"
CommandParameter="{Binding}"
Content="{ext:Locale Recover}"/>
Margin="5"
Command="{Binding Recover}"
CommandParameter="{Binding}">
<TextBlock Text="{ext:Locale UserProfiles_RecoverProfile}" />
</Button>
</Grid>
</Border>
</DataTemplate>
@ -56,12 +67,12 @@
<TextBlock
IsVisible="{Binding IsEmpty}"
TextAlignment="Center"
Text="{ext:Locale UserProfilesRecoverEmptyList}"/>
Text="{ext:Locale UserProfiles_RecoverProfile_EmptyList}"/>
</Panel>
</Border>
<StackPanel
Grid.Row="1"
Margin="0 24 0 0"
Margin="0,30,0,0"
Orientation="Horizontal">
<Button
Width="50"

View file

@ -27,11 +27,12 @@ namespace Ryujinx.Ava.UI.Views.User
switch (arg.NavigationMode)
{
case NavigationMode.New:
case NavigationMode.Back:
NavigationDialogHost parent = (NavigationDialogHost)arg.Parameter;
_parent = parent;
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.UserProfilesRecoverHeading]}";
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle]} - {LocaleManager.Instance[LocaleKeys.UserProfiles_RecoverLostProfiles]}";
break;
}
@ -42,10 +43,5 @@ namespace Ryujinx.Ava.UI.Views.User
{
_parent?.GoBack();
}
private void Recover(object sender, RoutedEventArgs e)
{
_parent?.RecoverLostAccounts();
}
}
}

View file

@ -22,9 +22,9 @@
<Grid RowDefinitions="Auto,*,Auto">
<Grid
Grid.Row="0"
Margin="0,0,0,5"
HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*">
<StackPanel
Margin="0,0,0,10"
Spacing="10"
Orientation="Horizontal"
HorizontalAlignment="Left"
@ -33,9 +33,9 @@
HorizontalContentAlignment="Left"
MinWidth="100">
<ComboBoxItem
Content="{ext:Locale Name}" />
Content="{ext:Locale UserProfiles_ManageSaves_SortByName}" />
<ComboBoxItem
Content="{ext:Locale Size}" />
Content="{ext:Locale UserProfiles_ManageSaves_SortBySize}" />
<ComboBox.Styles>
<Style Selector="ContentControl#ContentPresenter">
<Setter Property="HorizontalAlignment" Value="Left" />
@ -46,9 +46,9 @@
HorizontalContentAlignment="Left"
MinWidth="150">
<ComboBoxItem
Content="{ext:Locale OrderAscending}" />
Content="{ext:Locale UserProfiles_ManageSaves_SortOrderAscending}" />
<ComboBoxItem
Content="{ext:Locale OrderDescending}" />
Content="{ext:Locale UserProfiles_ManageSaves_SortOrderDescending}" />
<ComboBox.Styles>
<Style Selector="ContentControl#ContentPresenter">
<Setter Property="HorizontalAlignment" Value="Left" />
@ -59,18 +59,18 @@
<Grid
Grid.Column="1"
HorizontalAlignment="Stretch"
Margin="10,0, 0, 0" ColumnDefinitions="Auto,*">
<TextBlock Text="{ext:Locale Search}" VerticalAlignment="Center" />
Margin="20,0,0,10" ColumnDefinitions="Auto,*">
<TextBox
Margin="10,0,0,0"
Margin="5,0,0,0"
Grid.Column="1"
HorizontalAlignment="Stretch"
Text="{Binding Search}" />
Text="{Binding Search}"
Watermark="{ext:Locale UserProfiles_ManageSaves_Search}" />
</Grid>
</Grid>
<Border
Grid.Row="1"
Margin="0,5"
Padding="2.5"
BorderThickness="1"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
CornerRadius="5"
@ -84,7 +84,7 @@
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="10" />
<Setter Property="Margin" Value="5" />
<Setter Property="Margin" Value="0" />
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
@ -168,7 +168,7 @@
</Border>
<StackPanel
Grid.Row="2"
Margin="0 24 0 0"
Margin="0,30,0,0"
Orientation="Horizontal">
<Button
Width="50"

View file

@ -55,7 +55,7 @@ namespace Ryujinx.Ava.UI.Views.User
}
DataContext = ViewModel = new UserSaveManagerViewModel(_accountManager);
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {ViewModel.SaveManagerHeading}";
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle]} - {ViewModel.SaveManagerTitle}";
Task.Run(LoadSaves);
}
@ -127,8 +127,8 @@ namespace Ryujinx.Ava.UI.Views.User
{
if (button.DataContext is SaveModel saveModel)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DeleteUserSave],
LocaleManager.Instance[LocaleKeys.IrreversibleActionNote],
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.UserProfiles_DeleteSaveNote],
LocaleManager.Instance[LocaleKeys.UserProfiles_IrreversibleActionNote],
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
string.Empty);

View file

@ -19,28 +19,24 @@
<viewModels:UserProfileViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*,Auto">
<Border
CornerRadius="5"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1">
<Border Padding="-3" BorderThickness="0">
<ListBox
MaxHeight="300"
HorizontalAlignment="Stretch"
HorizontalAlignment="Center"
VerticalAlignment="Center"
SelectionChanged="ProfilesList_SelectionChanged"
Background="Transparent"
ItemsSource="{Binding Profiles}">
ItemsSource="{Binding Profiles}"
SelectionChanged="ProfilesList_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel
HorizontalAlignment="Left"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Margin" Value="5 5 0 5" />
<Setter Property="Margin" Value="0" />
<Setter Property="CornerRadius" Value="5" />
</Style>
<Style Selector="Rectangle#SelectionIndicator">
@ -54,6 +50,7 @@
PointerEntered="Grid_PointerEntered"
PointerExited="Grid_OnPointerExited">
<Border
Margin="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
@ -65,18 +62,20 @@
<Image
Width="96"
Height="96"
Margin="0,0,0,10"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Source="{Binding Image, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
<TextBlock
HorizontalAlignment="Stretch"
MaxWidth="90"
Text="{Binding Name}"
Height="30"
MaxWidth="90"
TextAlignment="Center"
TextWrapping="Wrap"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"
MaxLines="2"
Margin="5" />
TextWrapping="Wrap"
MaxLines="2" />
</StackPanel>
</Border>
<Border
@ -104,8 +103,8 @@
<DataTemplate
DataType="viewModels:BaseModel">
<Panel
Height="118"
Width="96">
Height="146"
Width="106">
<Button
MinWidth="50"
MinHeight="50"
@ -119,11 +118,6 @@
Click="AddUser">
<ui:SymbolIcon Symbol="Add" />
</Button>
<Panel.Styles>
<Style Selector="Panel">
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}"/>
</Style>
</Panel.Styles>
</Panel>
</DataTemplate>
</ListBox.DataTemplates>
@ -131,27 +125,29 @@
</Border>
<StackPanel
Grid.Row="1"
Margin="0 24 0 0"
Margin="0,30,0,0"
HorizontalAlignment="Left"
Orientation="Horizontal"
Spacing="10">
<Button
Click="ManageSaves">
<TextBlock Text="{ext:Locale UserProfilesManageSaves}" />
<TextBlock Text="{ext:Locale UserProfiles_ManageSaves}" />
</Button>
<Button
Click="RecoverLostAccounts">
<TextBlock Text="{ext:Locale UserProfilesRecoverLostAccounts}" />
<TextBlock
Text="{ext:Locale UserProfiles_RecoverLostProfiles}"
ToolTip.Tip="{ext:Locale UserProfiles_RecoverLostProfiles_ToolTip}" />
</Button>
</StackPanel>
<StackPanel
Grid.Row="1"
Margin="0 24 0 0"
Margin="0,30,0,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
Click="Close">
<TextBlock Text="{ext:Locale UserProfilesClose}" />
<TextBlock Text="{ext:Locale UserProfiles_ButtonClose}" />
</Button>
</StackPanel>
</Grid>

View file

@ -40,7 +40,7 @@ namespace Ryujinx.Ava.UI.Views.User
if (arg.NavigationMode == NavigationMode.Back)
{
((ContentDialog)_parent.Parent).Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle];
((ContentDialog)_parent.Parent).Title = LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle];
}
DataContext = ViewModel;