Merge branch 'release_sky' into 'master_sky'

Release sky

See merge request StarlockTeam/app-starlock!272
This commit is contained in:
李仪 2025-09-03 09:46:21 +00:00
commit 46c9bc7a29
84 changed files with 45667 additions and 44745 deletions

View File

@ -1160,5 +1160,11 @@
"锁语音包设置": "قفل مجموعة صوت", "锁语音包设置": "قفل مجموعة صوت",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "ذكر صوت", "男声": "ذكر صوت",
"女声": "صوت بنات" "女声": "صوت بنات",
"您的图像和视频数据仅保留": "يتم الاحتفاظ ببيانات الصور والفيديو فقط",
"后图像和视频数据将会失效,开通": "بعد ذلك ، ستكون بيانات الصورة والفيديو غير صالحة ويتم تنشيطها",
"云存会员": "عضوية التخزين السحابي",
"服务,图像视频信息随心存!": "معلومات الخدمة والصور والفيديو في قلبك!",
"图像": "صورة",
"视频": "فيديو"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Заключване на настройките на гласовия пакет", "锁语音包设置": "Заключване на настройките на гласовия пакет",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Мъжки глас", "男声": "Мъжки глас",
"女声": "Женски глас" "女声": "Женски глас",
"您的图像和视频数据仅保留": "Данните ви за изображения и видеоклипове се запазват само",
"后图像和视频数据将会失效,开通": "След това данните за изображението и видеото ще бъдат невалидни и активирани",
"云存会员": "Членство в Cloud Storage",
"服务,图像视频信息随心存!": "Информацията за обслужване, изображения и видео са във вашето сърце!",
"图像": "изображение",
"视频": "Видео"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "ভয়েস প্যাকেজ সেটিংস লক করুন", "锁语音包设置": "ভয়েস প্যাকেজ সেটিংস লক করুন",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "পুরুষের কণ্ঠ", "男声": "পুরুষের কণ্ঠ",
"女声": "নারী কণ্ঠ" "女声": "নারী কণ্ঠ",
"您的图像和视频数据仅保留": "আপনার চিত্র এবং ভিডিও ডেটা কেবল ধরে রাখা হয়",
"后图像和视频数据将会失效,开通": "এর পরে, চিত্র এবং ভিডিও ডেটা অবৈধ এবং সক্রিয় হবে",
"云存会员": "ক্লাউড স্টোরেজ সদস্যতা",
"服务,图像视频信息随心存!": "পরিষেবা, চিত্র এবং ভিডিও তথ্য আপনার হৃদয়ে!",
"图像": "প্রতিচ্ছবি",
"视频": "ভিডিও"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Zamknout nastavení hlasového balíčku", "锁语音包设置": "Zamknout nastavení hlasového balíčku",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Mužský hlas", "男声": "Mužský hlas",
"女声": "Ženský hlas" "女声": "Ženský hlas",
"您的图像和视频数据仅保留": "Uchovávají se pouze vaše obrazová data a data videí",
"后图像和视频数据将会失效,开通": "Poté budou obrazová a video data neplatná a aktivovaná",
"云存会员": "Členství v cloudovém úložišti",
"服务,图像视频信息随心存!": "Servis, obrazové a video informace jsou na prvním místě!",
"图像": "obraz",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Lås stemmepakkeindstillinger", "锁语音包设置": "Lås stemmepakkeindstillinger",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Mandlige stemmer", "男声": "Mandlige stemmer",
"女声": "Kvindelige stemmer" "女声": "Kvindelige stemmer",
"您的图像和视频数据仅保留": "Dine billed- og videodata opbevares kun",
"后图像和视频数据将会失效,开通": "Derefter vil billed- og videodataene være ugyldige og aktiveret",
"云存会员": "Medlemskab af Cloud Storage",
"服务,图像视频信息随心存!": "Service-, billed- og videoinformation er i dit hjerte!",
"图像": "billede",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Sperren von Sprachpaketeinstellungen", "锁语音包设置": "Sperren von Sprachpaketeinstellungen",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Männliche Stimme", "男声": "Männliche Stimme",
"女声": "Frauenstimme" "女声": "Frauenstimme",
"您的图像和视频数据仅保留": "Ihre Bild- und Videodaten werden nur dann aufbewahrt",
"后图像和视频数据将会失效,开通": "Danach sind die Bild- und Videodaten ungültig und aktiviert",
"云存会员": "Cloud-Speicher-Mitgliedschaft",
"服务,图像视频信息随心存!": "Service-, Bild- und Videoinformationen liegen Ihnen am Herzen!",
"图像": "Bild",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Ρυθμίσεις κλειδώματος πακέτου φωνής", "锁语音包设置": "Ρυθμίσεις κλειδώματος πακέτου φωνής",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Ανδρική φωνή", "男声": "Ανδρική φωνή",
"女声": "Γυναικεία φωνή" "女声": "Γυναικεία φωνή",
"您的图像和视频数据仅保留": "Τα δεδομένα εικόνας και βίντεο διατηρούνται μόνο",
"后图像和视频数据将会失效,开通": "Μετά από αυτό, τα δεδομένα εικόνας και βίντεο θα είναι άκυρα και θα ενεργοποιηθούν",
"云存会员": "Συνδρομή Cloud Storage",
"服务,图像视频信息随心存!": "Οι πληροφορίες εξυπηρέτησης, εικόνας και βίντεο είναι στην καρδιά σας!",
"图像": "εικόνα",
"视频": "Βίντεο"
} }

View File

@ -1167,5 +1167,11 @@
"锁语音包设置": "Lock voice package settings", "锁语音包设置": "Lock voice package settings",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "male voice", "男声": "male voice",
"女声": "female voice" "女声": "female voice",
"您的图像和视频数据仅保留": "Your image and video data is only retained",
"后图像和视频数据将会失效,开通": "After that, the image and video data will be invalid and activated",
"云存会员": "Cloud Storage Membership",
"服务,图像视频信息随心存!": "Service, image and video information are at your heart!",
"图像": "image",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Configuración del paquete de voz de bloqueo", "锁语音包设置": "Configuración del paquete de voz de bloqueo",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Voz masculina", "男声": "Voz masculina",
"女声": "Voz femenina" "女声": "Voz femenina",
"您的图像和视频数据仅保留": "Solo se conservan los datos de imagen y vídeo",
"后图像和视频数据将会失效,开通": "Después de eso, los datos de imagen y video no serán válidos y se activarán",
"云存会员": "Membresía de almacenamiento en la nube",
"服务,图像视频信息随心存!": "¡La información de servicio, imagen y video está en su corazón!",
"图像": "imagen",
"视频": "Vídeo"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Lukustage häälepaketi seaded", "锁语音包设置": "Lukustage häälepaketi seaded",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Meeste hääl", "男声": "Meeste hääl",
"女声": "Naiste hääl" "女声": "Naiste hääl",
"您的图像和视频数据仅保留": "Teie pildi- ja videoandmeid säilitatakse ainult",
"后图像和视频数据将会失效,开通": "Pärast seda on pildi- ja videoandmed kehtetud ja aktiveeritud",
"云存会员": "Pilvesalvestuse liikmelisus",
"服务,图像视频信息随心存!": "Teenindus-, pildi- ja videoteave on teie südames!",
"图像": "Piltide",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Lukitse äänipaketin asetukset", "锁语音包设置": "Lukitse äänipaketin asetukset",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Miehen ääni", "男声": "Miehen ääni",
"女声": "Naisten ääni" "女声": "Naisten ääni",
"您的图像和视频数据仅保留": "Kuva- ja videotietosi säilytetään vain",
"后图像和视频数据将会失效,开通": "Sen jälkeen kuva- ja videotiedot ovat virheellisiä ja aktivoituvat",
"云存会员": "Pilvitallennustilan jäsenyys",
"服务,图像视频信息随心存!": "Palvelu-, kuva- ja videotiedot ovat sydämessäsi!",
"图像": "kuva",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Paramètres du pack Lock Voice", "锁语音包设置": "Paramètres du pack Lock Voice",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Voix masculine", "男声": "Voix masculine",
"女声": "Voix de femmes" "女声": "Voix de femmes",
"您的图像和视频数据仅保留": "Vos données dimage et de vidéo ne sont conservées que",
"后图像和视频数据将会失效,开通": "Après cela, les données de limage et de la vidéo seront invalides et activées",
"云存会员": "Adhésion au stockage dans le cloud",
"服务,图像视频信息随心存!": "Le service, limage et les informations vidéo sont au cœur de vos préoccupations !",
"图像": "image",
"视频": "Vidéo"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "הגדרות חבילת קול לנעול", "锁语音包设置": "הגדרות חבילת קול לנעול",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "קול גבר", "男声": "קול גבר",
"女声": "קול נשי" "女声": "קול נשי",
"您的图像和视频数据仅保留": "נתוני התמונה והסרטונים נשמרים רק",
"后图像和视频数据将会失效,开通": "לאחר מכן, נתוני התמונה והווידאו לא יהיו חוקיים ויופעלו",
"云存会员": "חברות באחסון בענן",
"服务,图像视频信息随心存!": "מידע על שירות, תמונה ווידאו נמצאים בלב שלך!",
"图像": "תמונה",
"视频": "וידאו"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "आवाज पैकेज सेटिंग्स ताला लगाएँ", "锁语音包设置": "आवाज पैकेज सेटिंग्स ताला लगाएँ",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "पुरुष आवाज", "男声": "पुरुष आवाज",
"女声": "महिला आवाज" "女声": "महिला आवाज",
"您的图像和视频数据仅保留": "आपकी छवि और वीडियो डेटा केवल बनाए रखा जाता है",
"后图像和视频数据将会失效,开通": "उसके बाद, छवि और वीडियो डेटा अमान्य और सक्रिय हो जाएगा",
"云存会员": "क्लाउड स्टोरेज सदस्यता",
"服务,图像视频信息随心存!": "सेवा, छवि और वीडियो जानकारी आपके दिल में हैं!",
"图像": "प्रतिबिंब",
"视频": "वीडियो"
} }

View File

@ -1162,5 +1162,11 @@
"锁语音包设置": "鎖語音包設定", "锁语音包设置": "鎖語音包設定",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "男聲", "男声": "男聲",
"女声": "女聲" "女声": "女聲",
"您的图像和视频数据仅保留": "您的圖像和視頻數據僅保留",
"后图像和视频数据将会失效,开通": "后圖像和視頻數據將會失效,開通",
"云存会员": "雲存會員",
"服务,图像视频信息随心存!": "服務,圖像視頻資訊隨心存!",
"图像": "圖像",
"视频": "視頻"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Postavke zaključavanja glasovnog paketa", "锁语音包设置": "Postavke zaključavanja glasovnog paketa",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Muški glas", "男声": "Muški glas",
"女声": "Ženski glas" "女声": "Ženski glas",
"您的图像和视频数据仅保留": "Vaši podaci o slici i videozapisu zadržavaju se samo",
"后图像和视频数据将会失效,开通": "Nakon toga, slikovni i video podaci bit će nevažeći i aktivirani",
"云存会员": "Članstvo u pohrani u oblaku",
"服务,图像视频信息随心存!": "Informacije o usluzi, slikama i videozapisima su vam u srcu!",
"图像": "slika",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Hangcsomag zárolási beállításai", "锁语音包设置": "Hangcsomag zárolási beállításai",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Férfi hang", "男声": "Férfi hang",
"女声": "női hang" "女声": "női hang",
"您的图像和视频数据仅保留": "A kép- és videóadatokat csak a rendszer őrzi meg",
"后图像和视频数据将会失效,开通": "Ezt követően a kép- és videóadatok érvénytelenek lesznek és aktiválódnak",
"云存会员": "Felhőalapú tárolási tagság",
"服务,图像视频信息随心存!": "A szolgáltatás, a képi és videós információk a szívedben vannak!",
"图像": "kép",
"视频": "Video"
} }

View File

@ -1167,5 +1167,11 @@
"锁语音包设置": "Փակել ձայնային փաթեթի պարամետրերը", "锁语音包设置": "Փակել ձայնային փաթեթի պարամետրերը",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "տղամարդկանց ձայն", "男声": "տղամարդկանց ձայն",
"女声": "կանանց ձայն" "女声": "կանանց ձայն",
"您的图像和视频数据仅保留": "Ձեր պատկերի եւ վիդեո տվյալները պահպանվում են միայն",
"后图像和视频数据将会失效,开通": "Դրանից հետո պատկերի եւ վիդեո տվյալները կլինեն անվավեր եւ կակտիվացվեն",
"云存会员": "Cloud Storage Membership",
"服务,图像视频信息随心存!": "Ծառայությունը, պատկերը եւ վիդեո տեղեկատվությունը ձեր սրտում են:",
"图像": "Պատկերասրահ",
"视频": "Տեսանյութ"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Mengunci paket suara", "锁语音包设置": "Mengunci paket suara",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "6 tahun sebelumnya", "男声": "6 tahun sebelumnya",
"女声": "Suara wanita" "女声": "Suara wanita",
"您的图像和视频数据仅保留": "Data gambar dan video Anda hanya disimpan",
"后图像和视频数据将会失效,开通": "Setelah itu, data gambar dan video akan tidak valid dan diaktifkan",
"云存会员": "Keanggotaan Cloud Storage",
"服务,图像视频信息随心存!": "Informasi layanan, gambar, dan video adalah inti Anda!",
"图像": "citra",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Impostazioni pacchetto vocale blocco", "锁语音包设置": "Impostazioni pacchetto vocale blocco",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "voce maschile", "男声": "voce maschile",
"女声": "voce femminile" "女声": "voce femminile",
"您的图像和视频数据仅保留": "I dati delle immagini e dei video vengono conservati solo",
"后图像和视频数据将会失效,开通": "Successivamente, i dati dell'immagine e del video non saranno più validi e attivati",
"云存会员": "Iscrizione al cloud storage",
"服务,图像视频信息随心存!": "Le informazioni sul servizio, le immagini e i video sono al tuo centro!",
"图像": "immagine",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "ロック音声パケット設定", "锁语音包设置": "ロック音声パケット設定",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "男声", "男声": "男声",
"女声": "女声" "女声": "女声",
"您的图像和视频数据仅保留": "画像と動画のデータのみが保持されます",
"后图像和视频数据将会失效,开通": "その後、画像とビデオのデータは無効になり、アクティブになります",
"云存会员": "クラウドストレージメンバーシップ",
"服务,图像视频信息随心存!": "サービス、画像、ビデオ情報があなたの中心にあります!",
"图像": "画像",
"视频": "ビデオ"
} }

View File

@ -1167,5 +1167,11 @@
"锁语音包设置": "ხმის პაკეტის პარამეტრები", "锁语音包设置": "ხმის პაკეტის პარამეტრები",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "მამაკაცის ხმა", "男声": "მამაკაცის ხმა",
"女声": "ქალის ხმა" "女声": "ქალის ხმა",
"您的图像和视频数据仅保留": "თქვენი სურათი და ვიდეო მონაცემები ინახება მხოლოდ",
"后图像和视频数据将会失效,开通": "ამის შემდეგ, სურათისა და ვიდეო მონაცემები არასწორი და გააქტიურებული იქნება",
"云存会员": "Cloud Storage წევრობა",
"服务,图像视频信息随心存!": "მომსახურება, სურათი და ვიდეო ინფორმაცია თქვენს გულშია!",
"图像": "სურათი",
"视频": "ვიდეო"
} }

View File

@ -1172,5 +1172,11 @@
"语音包设置": "语音包设置", "语音包设置": "语音包设置",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "男声", "男声": "男声",
"女声": "女声" "女声": "女声",
"您的图像和视频数据仅保留": "您的图像和视频数据仅保留",
"后图像和视频数据将会失效,开通": "后图像和视频数据将会失效,开通",
"云存会员": "云存会员",
"服务,图像视频信息随心存!": "服务,图像视频信息随心存!",
"图像": "图像",
"视频": "视频"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Дауыстық бума параметрлерін құлыптау", "锁语音包设置": "Дауыстық бума параметрлерін құлыптау",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "ер дауысы", "男声": "ер дауысы",
"女声": "Әйел дауысы" "女声": "Әйел дауысы",
"您的图像和视频数据仅保留": "Сіздің кескініңіз бен бейне деректеріңіз тек сақталады",
"后图像和视频数据将会失效,开通": "Осыдан кейін кескін мен бейне деректер жарамсыз болып, белсендіріледі",
"云存会员": "Бұлтты сақтауға мүшелік",
"服务,图像视频信息随心存!": "Қызмет, бейне және бейне ақпарат сіздің жүрегіңізде жатыр!",
"图像": "кескіні",
"视频": "Бейне"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "음성팩 설정 잠금", "锁语音包设置": "음성팩 설정 잠금",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "남성", "男声": "남성",
"女声": "여성 목소리" "女声": "여성 목소리",
"您的图像和视频数据仅保留": "이미지 및 동영상 데이터만 보존됩니다.",
"后图像和视频数据将会失效,开通": "그 후 이미지 및 비디오 데이터는 유효하지 않고 활성화됩니다",
"云存会员": "클라우드 스토리지 멤버십",
"服务,图像视频信息随心存!": "서비스, 이미지 및 비디오 정보가 당신의 중심에 있습니다!",
"图像": "이미지",
"视频": "비디오"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Balso paketo nustatymų užrakinimas", "锁语音包设置": "Balso paketo nustatymų užrakinimas",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "vyriškas balsas", "男声": "vyriškas balsas",
"女声": "Moteriškas balsas" "女声": "Moteriškas balsas",
"您的图像和视频数据仅保留": "Vaizdo ir vaizdo įrašų duomenys saugomi tik",
"后图像和视频数据将会失效,开通": "Po to vaizdo ir vaizdo duomenys bus negaliojantys ir suaktyvinti",
"云存会员": "Debesies saugyklos narystė",
"服务,图像视频信息随心存!": "Aptarnavimas, vaizdas ir video informacija yra jūsų širdis!",
"图像": "vaizdas",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Balso paketo nustatymų užrakinimas", "锁语音包设置": "Balso paketo nustatymų užrakinimas",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "vyriškas balsas", "男声": "vyriškas balsas",
"女声": "Moteriškas balsas" "女声": "Moteriškas balsas",
"您的图像和视频数据仅保留": "Data imej dan video anda hanya dikekalkan",
"后图像和视频数据将会失效,开通": "Selepas itu, data imej dan video akan menjadi tidak sah dan diaktifkan",
"云存会员": "Keahlian Storan Awan",
"服务,图像视频信息随心存!": "Maklumat perkhidmatan, imej dan video adalah di hati anda!",
"图像": "Imej",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Instellingen voor spraakpakket vergrendelen", "锁语音包设置": "Instellingen voor spraakpakket vergrendelen",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "mannelijke stem", "男声": "mannelijke stem",
"女声": "Vrouwelijke stem" "女声": "Vrouwelijke stem",
"您的图像和视频数据仅保留": "Uw beeld- en videogegevens worden alleen bewaard",
"后图像和视频数据将会失效,开通": "Daarna zijn de afbeeldings- en videogegevens ongeldig en geactiveerd",
"云存会员": "Lidmaatschap voor cloudopslag",
"服务,图像视频信息随心存!": "Service-, beeld- en video-informatie staan bij u centraal!",
"图像": "beeld",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Ustawienia blokowania pakietu głosowego", "锁语音包设置": "Ustawienia blokowania pakietu głosowego",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Mężczyzna", "男声": "Mężczyzna",
"女声": "Głos kobiecy" "女声": "Głos kobiecy",
"您的图像和视频数据仅保留": "Dane obrazu i filmu są przechowywane tylko",
"后图像和视频数据将会失效,开通": "Po tym czasie dane obrazu i wideo zostaną nieważne i aktywowane",
"云存会员": "Członkostwo w usłudze Cloud Storage",
"服务,图像视频信息随心存!": "Informacje o serwisie, obrazie i wideo są w Twoim sercu!",
"图像": "obraz",
"视频": "Wideo"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Configurações do pacote de voz bloqueada", "锁语音包设置": "Configurações do pacote de voz bloqueada",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Voz masculina", "男声": "Voz masculina",
"女声": "voz feminina" "女声": "voz feminina",
"您的图像和视频数据仅保留": "Seus dados de imagem e vídeo são retidos apenas",
"后图像和视频数据将会失效,开通": "Depois disso, os dados de imagem e vídeo serão inválidos e ativados",
"云存会员": "Associação de armazenamento em nuvem",
"服务,图像视频信息随心存!": "Informações de serviço, imagem e vídeo estão no seu coração!",
"图像": "imagem",
"视频": "Vídeo"
} }

View File

@ -1166,5 +1166,11 @@
"语音包设置": "Configurações do pacote de voz", "语音包设置": "Configurações do pacote de voz",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Macho", "男声": "Macho",
"女声": "Garota" "女声": "Garota",
"您的图像和视频数据仅保留": "Seus dados de imagem e vídeo são retidos apenas",
"后图像和视频数据将会失效,开通": "Depois disso, os dados de imagem e vídeo serão inválidos e ativados",
"云存会员": "Associação de armazenamento em nuvem",
"服务,图像视频信息随心存!": "Informações de serviço, imagem e vídeo estão no seu coração!",
"图像": "imagem",
"视频": "Vídeo"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Configurarea pachetului vocal de blocare", "锁语音包设置": "Configurarea pachetului vocal de blocare",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "vocea bărbatului", "男声": "vocea bărbatului",
"女声": "Voce feminină" "女声": "Voce feminină",
"您的图像和视频数据仅保留": "Datele tale de imagine și video sunt păstrate numai",
"后图像和视频数据将会失效,开通": "După aceea, datele de imagine și video vor fi invalide și activate",
"云存会员": "Abonament de stocare în cloud",
"服务,图像视频信息随心存!": "Serviciile, imaginile și informațiile video sunt în centrul dumneavoastră!",
"图像": "imagine",
"视频": "Video"
} }

View File

@ -1165,5 +1165,11 @@
"锁语音包设置": "Запустить настройки голосового пакета", "锁语音包设置": "Запустить настройки голосового пакета",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Мужской голос", "男声": "Мужской голос",
"女声": "Женские голоса" "女声": "Женские голоса",
"您的图像和视频数据仅保留": "Ваши изображения и видеоданные сохраняются только",
"后图像和视频数据将会失效,开通": "После этого изображение и видео данные будут недействительными и активированы",
"云存会员": "Членство в облачном хранилище",
"服务,图像视频信息随心存!": "Сервисная, имиджевая и видеоинформация в Вашем сердце!",
"图像": "образ",
"视频": "Видео"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Zamknutie nastavení hlasového balíka", "锁语音包设置": "Zamknutie nastavení hlasového balíka",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "mužský hlas", "男声": "mužský hlas",
"女声": "Ženský hlas" "女声": "Ženský hlas",
"您的图像和视频数据仅保留": "Vaše údaje o obrázkoch a videách sa zachovajú iba",
"后图像和视频数据将会失效,开通": "Potom budú údaje o obrázku a videu neplatné a aktivované",
"云存会员": "Členstvo v cloudovom úložisku",
"服务,图像视频信息随心存!": "Informácie o službách, obrázkoch a videách sú vo vašom srdci!",
"图像": "obraz",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Закључајте подешавања говорног пакета", "锁语音包设置": "Закључајте подешавања говорног пакета",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "мушки глас", "男声": "мушки глас",
"女声": "женски глас" "女声": "женски глас",
"您的图像和视频数据仅保留": "Ваши подаци о слици и видео записима се задржавају само",
"后图像和视频数据将会失效,开通": "Након тога, сликовни и видео подаци ће бити неважећи и активирани",
"云存会员": "Чланство у облаку за складиштење",
"服务,图像视频信息随心存!": "Сервис , слике и видео информације су у вашем срцу!",
"图像": "Слика",
"视频": "Пријава"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Lås inställningar för röstpaket", "锁语音包设置": "Lås inställningar för röstpaket",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Mänsklig röst", "男声": "Mänsklig röst",
"女声": "Kvinnlig röst" "女声": "Kvinnlig röst",
"您的图像和视频数据仅保留": "Dina bild- och videodata sparas endast",
"后图像和视频数据将会失效,开通": "Efter det kommer bild- och videodata att vara ogiltiga och aktiverade",
"云存会员": "Medlemskap i molnlagring",
"服务,图像视频信息随心存!": "Service, bild- och videoinformation finns i ditt hjärta!",
"图像": "bild",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "ล็อคการตั้งค่า Voice Pack", "锁语音包设置": "ล็อคการตั้งค่า Voice Pack",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "เสียงผู้ชาย", "男声": "เสียงผู้ชาย",
"女声": "เสียงผู้หญิง" "女声": "เสียงผู้หญิง",
"您的图像和视频数据仅保留": "ระบบจะเก็บข้อมูลรูปภาพและวิดีโอของคุณไว้เท่านั้น",
"后图像和视频数据将会失效,开通": "หลังจากนั้น ข้อมูลรูปภาพและวิดีโอจะไม่ถูกต้องและเปิดใช้งาน",
"云存会员": "สมาชิกที่เก็บข้อมูลบนคลาวด์",
"服务,图像视频信息随心存!": "ข้อมูลบริการ รูปภาพ และวิดีโออยู่ที่หัวใจของคุณ!",
"图像": "ภาพ",
"视频": "วีดิทัศน์"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Ses Paketi Ayarlarını Kilitle", "锁语音包设置": "Ses Paketi Ayarlarını Kilitle",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Erkek Sesi", "男声": "Erkek Sesi",
"女声": "Kadın Sesi" "女声": "Kadın Sesi",
"您的图像和视频数据仅保留": "Görüntü ve video verileriniz yalnızca korunur",
"后图像和视频数据将会失效,开通": "Bundan sonra, görüntü ve video verileri geçersiz olacak ve etkinleştirilecektir",
"云存会员": "Bulut Depolama Üyeliği",
"服务,图像视频信息随心存!": "Servis, görüntü ve video bilgileri kalbinizde!",
"图像": "resim",
"视频": "Video"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "鎖語音包設定", "锁语音包设置": "鎖語音包設定",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "男聲", "男声": "男聲",
"女声": "女聲" "女声": "女聲",
"您的图像和视频数据仅保留": "您的圖像和視頻數據僅保留",
"后图像和视频数据将会失效,开通": "后圖像和視頻數據將會失效,開通",
"云存会员": "雲存會員",
"服务,图像视频信息随心存!": "服務,圖像視頻資訊隨心存!",
"图像": "圖像",
"视频": "視頻"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Параметри блокування голосового пакету", "锁语音包设置": "Параметри блокування голосового пакету",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Чоловічий голос", "男声": "Чоловічий голос",
"女声": "жіночий голос" "女声": "жіночий голос",
"您的图像和视频数据仅保留": "Ваші зображення та відеодані зберігаються лише",
"后图像和视频数据将会失效,开通": "Після цього дані зображення та відео будуть недійсними та активованими",
"云存会员": "Членство в хмарних сховищах",
"服务,图像视频信息随心存!": "Сервіс, зображення та відео інформація у вашому серці!",
"图像": "образ",
"视频": "Відео"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "صوتی پیکیج کی ترتیبات لاک کریں", "锁语音包设置": "صوتی پیکیج کی ترتیبات لاک کریں",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "مردوں کی آواز", "男声": "مردوں کی آواز",
"女声": "خواتین کی آواز" "女声": "خواتین کی آواز",
"您的图像和视频数据仅保留": "آپ کی تصویر اور ویڈیو کا ڈیٹا صرف برقرار رکھا گیا ہے",
"后图像和视频数据将会失效,开通": "اس کے بعد ، تصویر اور ویڈیو کا ڈیٹا غیر قانونی اور فعال ہوجائے گا۔",
"云存会员": "Cloud Storage Membership",
"服务,图像视频信息随心存!": "خدمت، تصویر اور ویڈیو کی معلومات آپ کے دل میں ہیں!",
"图像": "روپ",
"视频": "ویڈیو"
} }

View File

@ -1161,5 +1161,11 @@
"锁语音包设置": "Khóa cài đặt gói thoại", "锁语音包设置": "Khóa cài đặt gói thoại",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "Giọng nam", "男声": "Giọng nam",
"女声": "Giọng nữ" "女声": "Giọng nữ",
"您的图像和视频数据仅保留": "Dữ liệu hình ảnh và video của bạn chỉ được giữ lại",
"后图像和视频数据将会失效,开通": "Sau đó, dữ liệu hình ảnh và video sẽ không hợp lệ và được kích hoạt",
"云存会员": "Tư cách thành viên lưu trữ đám mây",
"服务,图像视频信息随心存!": "Thông tin dịch vụ, hình ảnh và video là trọng tâm của bạn!",
"图像": "ảnh",
"视频": "Video"
} }

View File

@ -63,7 +63,6 @@
"授权管理员拥有操作这把锁的重要权限,请确保只发给我你信任的人": "授权管理员拥有操作这把锁的重要权限,请确保只发给我你信任的人", "授权管理员拥有操作这把锁的重要权限,请确保只发给我你信任的人": "授权管理员拥有操作这把锁的重要权限,请确保只发给我你信任的人",
"功能开启后,你将可以通过网关远程开锁。此功能的开启和关闭只能在锁附近通过手机蓝牙进行。": "功能开启后,你将可以通过网关远程开锁。此功能的开启和关闭只能在锁附近通过手机蓝牙进行。", "功能开启后,你将可以通过网关远程开锁。此功能的开启和关闭只能在锁附近通过手机蓝牙进行。": "功能开启后,你将可以通过网关远程开锁。此功能的开启和关闭只能在锁附近通过手机蓝牙进行。",
"此功能的开启和关闭只能在锁附近通过手机蓝牙进行": "此功能的开启和关闭只能在锁附近通过手机蓝牙进行", "此功能的开启和关闭只能在锁附近通过手机蓝牙进行": "此功能的开启和关闭只能在锁附近通过手机蓝牙进行",
"功能开启后,你将可以通过网关远程开锁。": "功能开启后,你将可以通过网关远程开锁。", "功能开启后,你将可以通过网关远程开锁。": "功能开启后,你将可以通过网关远程开锁。",
"排列方式": "排列方式", "排列方式": "排列方式",
"早到榜": "早到榜", "早到榜": "早到榜",
@ -1102,7 +1101,7 @@
"支持的国家": "支持的国家", "支持的国家": "支持的国家",
"支持的国家值": "美国、加拿大、英国、澳大利亚、印度、德国、法国、意大利、西班牙、日本", "支持的国家值": "美国、加拿大、英国、澳大利亚、印度、德国、法国、意大利、西班牙、日本",
"操作流程": "操作流程", "操作流程": "操作流程",
"操作流程值":"1 用智能锁APP添加锁和网关\n\n2 在APP里开启锁的远程开锁功能这个功能默认是关闭的。如果没有这个选项则锁不支持Alexa \n\n3 在Alexa中添加Skill并用智能锁APP的账号和密码进行授权。授权成功后就可以发现账号下的设备\n\n4 在Alexa app里找到锁开启语音开锁的功能并设置语言密码\n\n5 可以通过Alexa操作锁了", "操作流程值": "1 用智能锁APP添加锁和网关\n\n2 在APP里开启锁的远程开锁功能这个功能默认是关闭的。如果没有这个选项则锁不支持Alexa \n\n3 在Alexa中添加Skill并用智能锁APP的账号和密码进行授权。授权成功后就可以发现账号下的设备\n\n4 在Alexa app里找到锁开启语音开锁的功能并设置语言密码\n\n5 可以通过Alexa操作锁了",
"Google Home": "Google Home", "Google Home": "Google Home",
"Action name": "Action name", "Action name": "Action name",
"ScienerSmart": "ScienerSmart", "ScienerSmart": "ScienerSmart",
@ -1174,5 +1173,11 @@
"语音包设置": "语音包设置", "语音包设置": "语音包设置",
"(中国台湾)": "(中国台湾)", "(中国台湾)": "(中国台湾)",
"男声": "男声", "男声": "男声",
"女声": "女声" "女声": "女声",
"您的图像和视频数据仅保留": "您的图像和视频数据仅保留",
"后图像和视频数据将会失效,开通": "后图像和视频数据将会失效,开通",
"云存会员": "云存会员",
"服务,图像视频信息随心存!": "服务,图像视频信息随心存!",
"图像": "图像",
"视频": "视频"
} }

View File

@ -0,0 +1,54 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart' as crypto;
import '../io_reply.dart';
import '../io_sender.dart';
import '../io_tool/io_tool.dart';
import '../io_type.dart';
import '../sm4Encipher/sm4.dart';
//oat升级
class ReadLockCurrentVoicePacket extends SenderProtocol {
ReadLockCurrentVoicePacket({
this.lockID,
}) : super(CommandType.readLockCurrentVoicePacket);
String? lockID;
@override
String toString() {
return 'ReadLockCurrentVoicePacket{lockID: $lockID}';
}
@override
List<int> messageDetail() {
List<int> data = <int>[];
//
final int type = commandType!.typeValue;
final double typeDouble = type / 256;
final int type1 = typeDouble.toInt();
final int type2 = type % 256;
data.add(type1);
data.add(type2);
// id 40
final int lockIDLength = utf8.encode(lockID!).length;
data.addAll(utf8.encode(lockID!));
data = getFixedLengthList(data, 40 - lockIDLength);
printLog(data);
return data;
}
}
class ReadLockCurrentVoicePacketReply extends Reply {
ReadLockCurrentVoicePacketReply.parseData(
CommandType commandType, List<int> dataDetail)
: super.parseData(commandType, dataDetail) {
data = dataDetail;
status = data[2];
errorWithStstus(status);
}
}

View File

@ -0,0 +1,61 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart' as crypto;
import '../io_reply.dart';
import '../io_sender.dart';
import '../io_tool/io_tool.dart';
import '../io_type.dart';
import '../sm4Encipher/sm4.dart';
//oat升级
class SetVoicePackageFinalResult extends SenderProtocol {
SetVoicePackageFinalResult({
this.lockID,
this.languageCode,
}) : super(CommandType.setLockCurrentVoicePacket);
String? lockID;
String? languageCode;
@override
String toString() {
return 'SetVoicePackageFinalResult{lockID: $lockID, languageCode: $languageCode}';
}
@override
List<int> messageDetail() {
List<int> data = <int>[];
//
final int type = commandType!.typeValue;
final double typeDouble = type / 256;
final int type1 = typeDouble.toInt();
final int type2 = type % 256;
data.add(type1);
data.add(type2);
// id 40
final int lockIDLength = utf8.encode(lockID!).length;
data.addAll(utf8.encode(lockID!));
data = getFixedLengthList(data, 40 - lockIDLength);
//languageCode 20
final int languageCodeLength = utf8.encode(languageCode!).length;
data.addAll(utf8.encode(languageCode!));
data = getFixedLengthList(data, 20 - languageCodeLength);
printLog(data);
return data;
}
}
class SetVoicePackageFinalResultReply extends Reply {
SetVoicePackageFinalResultReply.parseData(
CommandType commandType, List<int> dataDetail)
: super.parseData(commandType, dataDetail) {
data = dataDetail;
status = data[2];
errorWithStstus(status);
}
}

View File

@ -44,7 +44,9 @@ enum CommandType {
startVoicePackageConfigure, // 0x30A1 startVoicePackageConfigure, // 0x30A1
voicePackageConfigureProcess, // 0x30A2 voicePackageConfigureProcess, // 0x30A2
voicePackageConfigureConfirmation, // 0x30A3 voicePackageConfigureConfirmation, // 0x30A3
getDeviceModel, // 0x30A4 readLockCurrentVoicePacket, // 0x30A4
setLockCurrentVoicePacket, // 0x30A5
getDeviceModel, // 0x30A4
gatewayConfiguringWifi, // 0x30F4 gatewayConfiguringWifi, // 0x30F4
gatewayConfiguringWifiResult, // 0x30F5 gatewayConfiguringWifiResult, // 0x30F5
@ -210,7 +212,12 @@ extension ExtensionCommandType on CommandType {
break; break;
case 0x30A4: case 0x30A4:
{ {
type = CommandType.getDeviceModel; type = CommandType.readLockCurrentVoicePacket;
}
break;
case 0x30A5:
{
type = CommandType.setLockCurrentVoicePacket;
} }
break; break;
case 0x30F4: case 0x30F4:
@ -340,9 +347,12 @@ extension ExtensionCommandType on CommandType {
case CommandType.voicePackageConfigureConfirmation: case CommandType.voicePackageConfigureConfirmation:
type = 0x30A3; type = 0x30A3;
break; break;
case CommandType.getDeviceModel: case CommandType.readLockCurrentVoicePacket:
type = 0x30A4; type = 0x30A4;
break; break;
case CommandType.setLockCurrentVoicePacket:
type = 0x30A5;
break;
default: default:
type = 0x300A; type = 0x300A;
break; break;
@ -362,7 +372,8 @@ extension ExtensionCommandType on CommandType {
case CommandType.gatewayGetWifiList: case CommandType.gatewayGetWifiList:
case CommandType.gatewayConfiguringWifi: case CommandType.gatewayConfiguringWifi:
case CommandType.gatewayGetStatus: case CommandType.gatewayGetStatus:
case CommandType.getDeviceModel: case CommandType.readLockCurrentVoicePacket:
case CommandType.setLockCurrentVoicePacket:
// //
type = 0x20; type = 0x20;
break; break;
@ -476,7 +487,10 @@ extension ExtensionCommandType on CommandType {
t = '语音包配置确认'; t = '语音包配置确认';
break; break;
case 0x30A4: case 0x30A4:
t = '获取设备型号'; t = '读取锁当前语音包';
break;
case 0x30A5:
t = '设置锁当前语音包';
break; break;
default: default:
t = '读星锁状态信息'; t = '读星锁状态信息';

View File

@ -18,9 +18,11 @@ import 'package:star_lock/blue/io_protocol/io_processOtaUpgrade.dart';
import 'package:star_lock/blue/io_protocol/io_readAdminPassword.dart'; import 'package:star_lock/blue/io_protocol/io_readAdminPassword.dart';
import 'package:star_lock/blue/io_protocol/io_readSupportFunctionsNoParameters.dart'; import 'package:star_lock/blue/io_protocol/io_readSupportFunctionsNoParameters.dart';
import 'package:star_lock/blue/io_protocol/io_readSupportFunctionsWithParameters.dart'; import 'package:star_lock/blue/io_protocol/io_readSupportFunctionsWithParameters.dart';
import 'package:star_lock/blue/io_protocol/io_readVoicePackageFinalResult.dart';
import 'package:star_lock/blue/io_protocol/io_referEventRecordTime.dart'; import 'package:star_lock/blue/io_protocol/io_referEventRecordTime.dart';
import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsNoParameters.dart'; import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsNoParameters.dart';
import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsWithParameters.dart'; import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsWithParameters.dart';
import 'package:star_lock/blue/io_protocol/io_setVoicePackageFinalResult.dart';
import 'package:star_lock/blue/io_protocol/io_timing.dart'; import 'package:star_lock/blue/io_protocol/io_timing.dart';
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart'; import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart';
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart'; import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart';
@ -317,6 +319,18 @@ class CommandReciverManager {
commandType, data); commandType, data);
} }
break; break;
case CommandType.readLockCurrentVoicePacket:
{
reply =
ReadLockCurrentVoicePacketReply.parseData(commandType, data);
}
break;
case CommandType.setLockCurrentVoicePacket:
{
reply =
SetVoicePackageFinalResultReply.parseData(commandType, data);
}
break;
case CommandType.generalExtendedCommond: case CommandType.generalExtendedCommond:
{ {
// //

View File

@ -80,7 +80,11 @@ class _StarLockLoginPageState extends State<StarLockLoginPage> {
), ),
], ],
), ),
body: ListView( body: GestureDetector(
onTap: (){
FocusScope.of(context).unfocus();
},
child: ListView(
padding: EdgeInsets.only(top: 120.h, left: 40.w, right: 40.w), padding: EdgeInsets.only(top: 120.h, left: 40.w, right: 40.w),
children: <Widget>[ children: <Widget>[
Container( Container(
@ -305,6 +309,7 @@ class _StarLockLoginPageState extends State<StarLockLoginPage> {
], ],
), ),
], ],
),
)); ));
} }

View File

@ -62,10 +62,8 @@ FutureOr<void> main() async {
} }
}); });
// //ToDo: // ios则初始化获取到voip token
// runApp(MultiProvider(providers: [ // token callkit
// ChangeNotifierProvider(create: (_) => DebugInfoModel()),
// ], child: MyApp(isLogin: isLogin)));
if (Platform.isIOS) { if (Platform.isIOS) {
CallKitHandler.setupListener(); CallKitHandler.setupListener();
String? token = await CallKitHandler.getVoipToken(); String? token = await CallKitHandler.getVoipToken();
@ -111,20 +109,4 @@ Future<void> privacySDKInitialization() async {
await jpushProvider.initJPushService(); await jpushProvider.initJPushService();
NotificationService().init(); // NotificationService().init(); //
// /// ip如果属于国内才进行初始化
// final CheckIPEntity entity = await ApiRepository.to.checkIpAction(ip: '');
// if (entity.errorCode!.codeIsSuccessful) {
// String currentLanguage =
// CurrentLocaleTool.getCurrentLocaleString(); //
// // ip是国内的且选的是中文才初始化一键登录
// if (entity.data!.abbreviation?.toLowerCase() == 'cn' &&
// currentLanguage == 'zh_CN') {
// //
// final StarLockLoginLogic loginLogic = Get.put(StarLockLoginLogic());
// await JverifyOneClickLoginManage();
// loginLogic.state.isCheckVerifyEnable.value =
// await JverifyOneClickLoginManage().checkVerifyEnable();
// eventBus.fire(AgreePrivacyAgreement());
// }
// }
} }

View File

@ -0,0 +1,11 @@
extension DateTimeExtensions on DateTime {
/// DateTime 00:00:00.000
DateTime get withoutTime {
return DateTime(year, month, day);
}
///
bool isSameDate(DateTime other) {
return year == other.year && month == other.month && day == other.day;
}
}

View File

@ -125,4 +125,9 @@ class DoorLockLogDataItem {
data['recordDetailStr'] = recordDetailStr; data['recordDetailStr'] = recordDetailStr;
return data; return data;
} }
@override
String toString() {
return 'DoorLockLogDataItem{recordId: $recordId, lockId: $lockId, lockAlias: $lockAlias, recordType: $recordType, recordTypeName: $recordTypeName, username: $username, operateDate: $operateDate, imagesUrl: $imagesUrl, videoUrl: $videoUrl, headUrl: $headUrl, userid: $userid, keyboardPwd: $keyboardPwd, recordStr: $recordStr, recordDetailStr: $recordDetailStr}';
}
} }

View File

@ -3,11 +3,15 @@ import 'dart:async';
import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:star_lock/apm/apm_helper.dart'; import 'package:star_lock/apm/apm_helper.dart';
import 'package:star_lock/appRouters.dart';
import 'package:star_lock/app_settings/app_settings.dart'; import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/common/XSConstantMacro/XSConstantMacro.dart';
import 'package:star_lock/main/lockDetail/doorLockLog/date_time_extensions.dart';
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_entity.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_entity.dart';
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_state.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_state.dart';
import 'package:star_lock/main/lockDetail/lockOperatingRecord/lockOperatingRecordGetLastRecordTime_entity.dart'; import 'package:star_lock/main/lockDetail/lockOperatingRecord/lockOperatingRecordGetLastRecordTime_entity.dart';
import 'package:star_lock/mine/valueAddedServices/advancedFeaturesWeb/advancedFeaturesWeb_entity.dart';
import 'package:star_lock/tools/commonDataManage.dart'; import 'package:star_lock/tools/commonDataManage.dart';
import 'package:star_lock/tools/dateTool.dart'; import 'package:star_lock/tools/dateTool.dart';
import 'package:star_lock/tools/eventBusEventManage.dart'; import 'package:star_lock/tools/eventBusEventManage.dart';
@ -235,13 +239,15 @@ class DoorLockLogLogic extends BaseGetXController {
lockId: state.keyInfos.value.lockId!, lockId: state.keyInfos.value.lockId!,
lockEventType: state.dropdownValue.value, lockEventType: state.dropdownValue.value,
pageNo: pageNo, pageNo: pageNo,
pageSize: int.parse(pageSize), pageSize: 1000,
startDate: state.startDate.value, startDate: state.startDate.value,
endDate: state.endDate.value); endDate: state.endDate.value);
if (entity.errorCode!.codeIsSuccessful) { if (entity.errorCode!.codeIsSuccessful) {
// //
state.lockLogItemList.addAll(entity.data!.itemList!); state.lockLogItemList.addAll(entity.data!.itemList!);
state.lockLogItemList.refresh(); state.lockLogItemList.refresh();
state.weekEventList.addAll(entity.data!.itemList!);
state.weekEventList.refresh();
// //
pageNo++; pageNo++;
} }
@ -358,6 +364,7 @@ class DoorLockLogLogic extends BaseGetXController {
@override @override
Future<void> onInit() async { Future<void> onInit() async {
_setWeekRange();
super.onInit(); super.onInit();
// //
@ -370,6 +377,48 @@ class DoorLockLogLogic extends BaseGetXController {
} }
} }
void _setWeekRange() {
final now = DateTime.now();
// 1=7=
int weekday = now.weekday; // 1-7
//
// : 0, : 1, ..., : 6
int daysToSubtract = weekday - 1; // 1
// 00:00:00.000
DateTime startOfWeek = DateTime(now.year, now.month, now.day)
.subtract(Duration(days: daysToSubtract));
// 23:59:59.999
DateTime endOfWeek = startOfWeek
.add(Duration(days: 6)) // 6
.add(Duration(hours: 23, minutes: 59, seconds: 59, milliseconds: 999));
//
state.startDate.value = startOfWeek.millisecondsSinceEpoch;
state.endDate.value = endOfWeek.millisecondsSinceEpoch;
}
//
void refreshWeek() {
_setWeekRange();
}
getWebPlayUrl() async {
final AdvancedFeaturesWebEntity entity =
await ApiRepository.to.getServicePackageBuyUrl();
if (entity.errorCode!.codeIsSuccessful) {
state.cloudStorageWebViewUrl.value = entity.data!.cloudStorage!;
final uploadReportBuyRequest = await ApiRepository.to
.uploadReportBuyRequest(lockId: state.keyInfos.value.lockId!);
if (uploadReportBuyRequest.errorCode!.codeIsSuccessful) {
Get.toNamed(Routers.advancedFeaturesWebPage, arguments: <String, int>{
'webBuyType': XSConstantMacro.webBuyTypeCloudStorage,
});
}
}
}
@override @override
Future<void> onClose() async { Future<void> onClose() async {
super.onClose(); super.onClose();

View File

@ -1,14 +1,18 @@
import 'package:flustars/flustars.dart'; import 'package:flustars/flustars.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:star_lock/appRouters.dart'; import 'package:star_lock/appRouters.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/main/lockDetail/doorLockLog/date_time_extensions.dart';
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_entity.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_entity.dart';
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_logic.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_logic.dart';
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_state.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_state.dart';
import 'package:star_lock/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_page.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_page.dart';
import 'package:star_lock/main/lockDetail/doorLockLog/week_calendar_view.dart';
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart';
import 'package:star_lock/main/lockDetail/videoLog/widget/full_screenImage_page.dart'; import 'package:star_lock/main/lockDetail/videoLog/widget/full_screenImage_page.dart';
import 'package:star_lock/main/lockDetail/videoLog/widget/video_thumbnail_image.dart'; import 'package:star_lock/main/lockDetail/videoLog/widget/video_thumbnail_image.dart';
@ -34,8 +38,39 @@ class DoorLockLogPage extends StatefulWidget {
} }
class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware { class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
final ScrollController _scrollController = ScrollController();
final DoorLockLogLogic logic = Get.put(DoorLockLogLogic()); final DoorLockLogLogic logic = Get.put(DoorLockLogLogic());
final DoorLockLogState state = Get.find<DoorLockLogLogic>().state; final DoorLockLogState state = Get.find<DoorLockLogLogic>().state;
bool _isAtBottom = false;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
void _onScroll() {
final max = _scrollController.position.maxScrollExtent;
final current = _scrollController.position.pixels;
AppLog.log('current:${current}');
// 5
if (current >= max - 5) {
if (!_isAtBottom) {
setState(() {
_isAtBottom = true;
});
print('✅ 已滑动到 timelines 列表底部!');
//
}
} else {
if (_isAtBottom) {
setState(() {
_isAtBottom = false;
});
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -97,16 +132,31 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
topAdvancedCalendarWidget(), topAdvancedCalendarWidget(),
Divider(
height: 1,
color: AppColors.greyLineColor,
indent: 30.w,
endIndent: 30.w,
),
eventDropDownWidget(), eventDropDownWidget(),
Expanded(child: timeLineView()) Expanded(child: timeLineView())
], ],
), ),
floatingActionButton: Visibility(
visible: _isAtBottom,
child: FloatingActionButton(
onPressed: () {
_scrollController.animateTo(
0.0,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(48.w),
),
backgroundColor: AppColors.mainColor,
child: Icon(
Icons.arrow_upward,
color: Colors.white,
size: 48.w,
),
),
),
); );
} }
@ -152,88 +202,14 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
} }
} }
// switch (value) {
// case "读取记录".tr:
// {
// logic.mockNetworkDataRequest(isRefresh: true);
// }
// break;
// case '清空记录'.tr:
// {
// ShowCupertinoAlertView().showClearOperationRecordAlert(
// clearClick: () {
// logic.clearOperationRecordRequest();
// });
// }
// break;
// case '导出记录':
// {
// showDialog(
// context: context,
// builder: (BuildContext context) {
// return ExportRecordDialog(
// onExport: (String filePath) {
// Get.toNamed(Routers.exportSuccessPage,
// arguments: <String, String>{'filePath': filePath});
// },
// );
// },
// );
// }
// break;
// }
// }
// //
Widget topAdvancedCalendarWidget() { Widget topAdvancedCalendarWidget() {
final ThemeData theme = Theme.of(context); return Container(
return Theme( margin: EdgeInsets.only(top: 20.h, left: 30.w, bottom: 10.h, right: 20.w),
data: theme.copyWith( child: Column(
textTheme: theme.textTheme.copyWith( crossAxisAlignment: CrossAxisAlignment.start,
titleMedium: theme.textTheme.titleMedium!.copyWith( children: [
fontSize: 16, _buildWeekCalendar(),
color: theme.colorScheme.secondary,
),
bodyLarge: theme.textTheme.bodyLarge!.copyWith(
fontSize: 14,
color: Colors.black54,
),
bodyMedium: theme.textTheme.bodyMedium!.copyWith(
fontSize: 12,
color: Colors.black87,
),
),
primaryColor: AppColors.mainColor,
highlightColor: Colors.yellow,
disabledColor: Colors.grey,
),
child: Stack(
children: <Widget>[
AdvancedCalendar(
controller: state.calendarControllerCustom,
events: state.events,
weekLineHeight: 48.0,
startWeekDay: 1,
innerDot: true,
keepLineSize: true,
calendarTextStyle: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w400,
height: 1.3125,
letterSpacing: 0,
),
),
Positioned(
top: 8.0,
right: 8.0,
child: Obx(() => Text(
'${state.currentSelectDate.value.year}${''.tr}${state.currentSelectDate.value.month}${''.tr}',
style: theme.textTheme.titleMedium!.copyWith(
fontSize: 16,
color: theme.colorScheme.secondary,
),
)),
),
], ],
), ),
); );
@ -269,15 +245,10 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(16.w), borderRadius: BorderRadius.circular(16.w),
), ),
child: Obx(() => EasyRefreshTool( child: Obx(
onRefresh: () async { () => state.lockLogItemList.isNotEmpty
logic.mockNetworkDataRequest(isRefresh: true);
},
onLoad: () async {
logic.mockNetworkDataRequest(isRefresh: false);
},
child: state.lockLogItemList.isNotEmpty
? Timeline.tileBuilder( ? Timeline.tileBuilder(
controller: _scrollController,
builder: _timelineBuilderWidget(), builder: _timelineBuilderWidget(),
theme: TimelineThemeData( theme: TimelineThemeData(
nodePosition: 0.04, // nodePosition: 0.04, //
@ -293,14 +264,19 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
), ),
), ),
) )
: NoData())), : NoData(),
),
); );
} }
String formatTimestampToHHmm(int timestampMs) { String formatTimestampToDateTimeYYYYMMDD(int timestampMs) {
// 1. DateTime DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestampMs);
int timestampSec = timestampMs ~/ 1000; DateFormat formatter =
DateFormat('MM${''.tr}dd${''.tr}'); // 2025-08-18 14:30
return formatter.format(dateTime);
}
String formatTimestampToHHmm(int timestampMs) {
// 2. DateTime // 2. DateTime
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestampMs); DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestampMs);
@ -309,6 +285,17 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
return formatter.format(dateTime); return formatter.format(dateTime);
} }
bool _checkIsVideoOrImagesType(DoorLockLogDataItem item) {
final recordType = item.recordType;
switch (recordType) {
case 130:
case 220:
return true;
default:
return false;
}
}
String _buildIDByType(DoorLockLogDataItem item) { String _buildIDByType(DoorLockLogDataItem item) {
final recordType = item.recordType; final recordType = item.recordType;
switch (recordType) { switch (recordType) {
@ -325,7 +312,8 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
return '${formatTimestampToHHmm(item.operateDate!)} ' + return '${formatTimestampToHHmm(item.operateDate!)} ' +
'密码'.tr + '密码'.tr +
'开锁'.tr + '开锁'.tr +
'${'昵称'.tr}${item.username}'+'${'密码'.tr}${item.keyboardPwd}'; '${'昵称'.tr}${item.username}' +
'${'密码'.tr}${item.keyboardPwd}';
case 30: case 30:
return '${formatTimestampToHHmm(item.operateDate!)} ' + return '${formatTimestampToHHmm(item.operateDate!)} ' +
''.tr + ''.tr +
@ -431,6 +419,20 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
itemCount: state.lockLogItemList.length, itemCount: state.lockLogItemList.length,
contentsBuilder: (BuildContext context, int index) { contentsBuilder: (BuildContext context, int index) {
final DoorLockLogDataItem timelineData = state.lockLogItemList[index]; final DoorLockLogDataItem timelineData = state.lockLogItemList[index];
// 👇 videoUrl build
int? firstVideoIndex = state.lockLogItemList
.indexWhere((item) => _checkIsVideoOrImagesType(item));
bool isInvalid = _checkIsVideoOrImagesType(timelineData) &&
((timelineData.imagesUrl == null &&
timelineData.videoUrl == null) ||
(timelineData.videoUrl == '' && timelineData.imagesUrl == ''));
String typeText = '';
if (timelineData.recordType == 130) {
typeText = '图像'.tr;
} else if (timelineData.recordType == 220) {
typeText = '视频'.tr;
}
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
Get.toNamed( Get.toNamed(
@ -444,20 +446,45 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text(
'${formatTimestampToDateTimeYYYYMMDD(timelineData.operateDate!)}',
style: TextStyle(
fontSize: 20.sp,
)),
// 使 SingleChildScrollView // 使 SingleChildScrollView
SingleChildScrollView( SingleChildScrollView(
scrollDirection: Axis.horizontal, // scrollDirection: Axis.horizontal, //
child: Text( child: RichText(
_buildIDByType(timelineData),
textAlign: TextAlign.left, textAlign: TextAlign.left,
text: TextSpan(
style: TextStyle( style: TextStyle(
color: _buildTextColorByType(timelineData), color: _buildTextColorByType(timelineData),
fontSize: 24.sp, fontSize: 24.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
// children: [
TextSpan(
text: _buildIDByType(timelineData) +
(isInvalid
? '${typeText}' +
'已失效'.tr +
''
: ''),
),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Visibility(
visible: isInvalid,
child: Icon(
Icons.error,
size: 24.sp,
color: Colors.red,
),
),
),
],
),
maxLines: 1, maxLines: 1,
//
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
@ -474,8 +501,71 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
), ),
), ),
SizedBox( SizedBox(
height: 20.h, height: 12.h,
), ),
Visibility(
visible: _checkIsVideoOrImagesType(timelineData) &&
index == firstVideoIndex,
child: GestureDetector(
onTap: () async {
await logic.getWebPlayUrl();
},
child: Container(
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.r)),
),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
children: [
//
TextSpan(
text:
'${'您的图像和视频数据仅保留'.tr} ${state.rollingStorageDays.value} ${''.tr} ,${state.rollingStorageDays.value} ${''.tr} ${'后图像和视频数据将会失效,开通'.tr}',
style: TextStyle(
color: Colors.grey,
fontSize: 16.sp,
fontWeight: FontWeight.w600,
height: 1.8,
),
),
// 🔥
TextSpan(
text: '云存会员'.tr,
style: TextStyle(
color: AppColors.mainColor,
fontSize: 22.sp,
fontWeight: FontWeight.w800,
//
decoration: TextDecoration.underline,
decorationThickness: 1.5,
height: 1.8,
),
recognizer: TapGestureRecognizer()
..onTap = () async {
// 👉
print('点击了“云存会员”');
await logic.getWebPlayUrl();
// Navigator.push(context, MaterialPageRoute(builder: ...));
},
),
//
TextSpan(
text: '服务,图像视频信息随心存!'.tr,
style: TextStyle(
color: Colors.grey,
fontSize: 16.sp,
fontWeight: FontWeight.w600,
height: 1.8,
),
),
],
),
),
),
),
)
], ],
), ),
), ),
@ -622,4 +712,56 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
} }
state.ifCurrentScreen.value = false; state.ifCurrentScreen.value = false;
} }
List<DateTime> getCurrentWeekDates() {
final now = DateTime.now();
// weekday: 1=, 2=, ..., 7=
//
// weekday == 7 0
final int daysSinceSunday = now.weekday % 7; // =1 -> %7=1, =7 -> %7=0
final List<DateTime> weekDates = [];
for (int i = 0; i < 7; i++) {
final DateTime day = DateTime(
now.year,
now.month,
now.day - daysSinceSunday + i, //
);
weekDates.add(day);
}
return weekDates;
}
Widget _buildWeekCalendar() {
return Obx(() {
final list = state.weekEventList.value;
final dateSet = list
.map((e) => DateTime.fromMillisecondsSinceEpoch(e.operateDate!))
.map((dt) => dt.withoutTime) //
.toSet(); // Set
AppLog.log('dateSet:${dateSet}');
return WeekCalendarView(
hasData: (DateTime date) {
return dateSet.contains(date.withoutTime);
},
onDateSelected: (DateTime date) async {
print('外部收到选中: $date');
state.operateDate = date.millisecondsSinceEpoch;
state.startDate.value =
DateTime(date.year, date.month, date.day).millisecondsSinceEpoch;
state.endDate.value =
DateTime(date.year, date.month, date.day, 23, 59, 59, 999)
.millisecondsSinceEpoch;
await logic.mockNetworkDataRequest(isRefresh: true);
},
onWeekChanged: (DateTime start, DateTime end) {
state.startDate.value = start.millisecondsSinceEpoch;
state.endDate.value = end.millisecondsSinceEpoch;
logic.mockNetworkDataRequest(isRefresh: true);
},
);
});
}
} }

View File

@ -1,5 +1,5 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get/get_rx/get_rx.dart';
import 'package:star_lock/common/XSConstantMacro/XSConstantMacro.dart'; import 'package:star_lock/common/XSConstantMacro/XSConstantMacro.dart';
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_entity.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_entity.dart';
import 'package:star_lock/tools/advancedCalendar/src/controller.dart'; import 'package:star_lock/tools/advancedCalendar/src/controller.dart';
@ -13,10 +13,15 @@ class DoorLockLogState {
DoorLockLogState() { DoorLockLogState() {
keyInfos.value = Get.arguments['keyInfo']; keyInfos.value = Get.arguments['keyInfo'];
} }
final Rx<DoorLockLogEntity> lockLogEntity = DoorLockLogEntity().obs; final Rx<DoorLockLogEntity> lockLogEntity = DoorLockLogEntity().obs;
final Rx<LockListInfoItemEntity> keyInfos = LockListInfoItemEntity().obs; final Rx<LockListInfoItemEntity> keyInfos = LockListInfoItemEntity().obs;
final RxList<DoorLockLogDataItem> lockLogItemList = final RxList<DoorLockLogDataItem> lockLogItemList =
<DoorLockLogDataItem>[].obs; <DoorLockLogDataItem>[].obs;
final RxList<DoorLockLogDataItem> weekEventList =
<DoorLockLogDataItem>[].obs;
final RxList<DoorLockLogDataItem> dayEventList =
<DoorLockLogDataItem>[].obs;
final AdvancedCalendarController calendarControllerToday = final AdvancedCalendarController calendarControllerToday =
AdvancedCalendarController.today(); AdvancedCalendarController.today();
final AdvancedCalendarController calendarControllerCustom = final AdvancedCalendarController calendarControllerCustom =
@ -69,4 +74,6 @@ class DoorLockLogState {
int logCountPage = 10; // int logCountPage = 10; //
Rx<DateTime> currentSelectDate = DateTime.now().obs; Rx<DateTime> currentSelectDate = DateTime.now().obs;
bool isLockReceiveResponse = false; // bool isLockReceiveResponse = false; //
RxString cloudStorageWebViewUrl = ''.obs;
RxInt rollingStorageDays = 3.obs; //
} }

View File

@ -0,0 +1,220 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:star_lock/app_settings/app_colors.dart';
import 'package:star_lock/main/lockDetail/doorLockLog/date_time_extensions.dart';
class WeekCalendarView extends StatefulWidget {
//
final bool Function(DateTime date)? hasData;
final void Function(DateTime date)? onDateSelected; //
final void Function(DateTime start, DateTime end)? onWeekChanged;
const WeekCalendarView({
Key? key,
this.hasData,
this.onDateSelected,
this.onWeekChanged,
}) : super(key: key);
@override
_WeekCalendarViewState createState() => _WeekCalendarViewState();
}
class _WeekCalendarViewState extends State<WeekCalendarView> {
final PageController _pageController = PageController(initialPage: 500);
int _currentPage = 500;
// DateTime
late DateTime _selectedDate;
@override
void initState() {
super.initState();
_selectedDate = DateTime.now().withoutTime; //
}
// page
List<DateTime> _getWeekDatesForPage(int page) {
final now = DateTime.now();
final baseSunday =
DateTime(now.year, now.month, now.day - (now.weekday % 7));
final daysOffset = (page - 500) * 7;
final targetSunday = baseSunday.add(Duration(days: daysOffset));
return List.generate(
7,
(i) => DateTime(
targetSunday.year, targetSunday.month, targetSunday.day + i));
}
//
bool _isToday(DateTime date) {
final now = DateTime.now();
return date.year == now.year &&
date.month == now.month &&
date.day == now.day;
}
//
bool _isSelected(DateTime date) {
return date.year == _selectedDate.year &&
date.month == _selectedDate.month &&
date.day == _selectedDate.day;
}
//
bool _hasData(DateTime date) {
return widget.hasData?.call(date.withoutTime) ?? false;
}
void _onDateSelected(DateTime date) {
setState(() {
_selectedDate = date.withoutTime;
});
//
widget.onDateSelected?.call(date);
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
_buildWeekRangeLabel(_currentPage),
SizedBox(height: 10.h),
SizedBox(
height: 100.h,
child: PageView.builder(
controller: _pageController,
itemCount: 1000,
itemBuilder: (context, page) {
final weekDates = _getWeekDatesForPage(page);
return Row(
children: weekDates.asMap().entries.map((entry) {
final int index = entry.key;
final DateTime date = entry.value;
final bool isSelected = _isSelected(date);
final bool hasData = _hasData(date);
final bool isToday = _isToday(date);
//
Color textColor;
if (isSelected) {
textColor = Colors.white; //
} else if (hasData) {
textColor = Colors.black; //
} else if (isToday) {
textColor = Colors.black; //
} else {
textColor = Colors.grey; //
}
//
Color? bgColor;
if (isSelected) {
bgColor = AppColors.mainColor; //
}
//
return GestureDetector(
onTap: () => _onDateSelected(date),
child: Container(
padding: EdgeInsets.all(4.w),
width: 75.w,
height: 75.w,
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(50.r),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
[
'简写周日',
'简写周一',
'简写周二',
'简写周三',
'简写周四',
'简写周五',
'简写周六'
][index]
.tr,
style: TextStyle(
fontSize: 14.sp,
color: textColor,
fontWeight: FontWeight.w400,
),
),
Text(
date.day.toString(),
style: TextStyle(
fontSize: 26.sp,
color: textColor,
fontWeight: FontWeight.w600,
),
),
if (isToday && !isSelected) //
SizedBox(height: 2.h),
if (isToday && !isSelected)
Container(
width: 6.w,
height: 6.w,
decoration: BoxDecoration(
color: AppColors.mainColor,
shape: BoxShape.circle,
),
),
],
),
),
);
}).toList(),
);
},
onPageChanged: (page) {
setState(() {
_currentPage = page;
});
//
final dates = _getWeekDatesForPage(page);
final startOfWeek = dates.first;
final endOfWeek = dates.last;
//
widget.onWeekChanged?.call(startOfWeek, endOfWeek);
},
),
),
],
);
}
Widget _buildWeekRangeLabel(int page) {
final dates = _getWeekDatesForPage(page);
final start = dates[0];
final end = dates[6];
String label;
if (start.year == end.year) {
// "2025年8月18日 - 8月24日"
label =
'${start.year}${''.tr}${start.month}${''.tr}${start.day}${''.tr} - ${end.month}${''.tr}${end.day}${''.tr}';
} else {
// "2024年12月31日 - 2025年1月6日"
label =
'${start.year}${''.tr}${start.month}${''.tr}${start.day}${''.tr} - ${end.year}${''.tr}${end.month}${''.tr}${end.day}${''.tr}';
}
return Text(
label,
style: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.w600),
);
}
}

View File

@ -253,10 +253,10 @@ class AddFingerprintLogic extends BaseGetXController {
final List<int> getTokenList = changeStringListToIntList(token!); final List<int> getTokenList = changeStringListToIntList(token!);
String startTime = DateTool().dateToHNString(state.effectiveDateTime.value); String startTime = DateTool().dateToHNString(state.effectiveDateTime.value);
String endTime = DateTool().dateToHNString(state.failureDateTime.value); String endTime = DateTool().dateToHNString(state.failureDateTime.value);
if (F.isSKY) { // if (F.isSKY) {
startTime = '255:00'; // startTime = '255:00';
endTime = '255:00'; // endTime = '255:00';
} // }
final String command = SenderAddFingerprintWithTimeCycleCoercionCommand( final String command = SenderAddFingerprintWithTimeCycleCoercionCommand(
keyID: '1', keyID: '1',

View File

@ -89,27 +89,27 @@ class _CatEyeCustomModePageState extends State<CatEyeCustomModePage> {
SizedBox( SizedBox(
height: 30.h, height: 30.h,
), ),
Container( // Container(
margin: EdgeInsets.only(left: 20.w), // margin: EdgeInsets.only(left: 20.w),
child: CommonItem( // child: CommonItem(
leftTitel: '实时画面'.tr, // leftTitel: '实时画面'.tr,
rightTitle: state.realTimeMode.value, // rightTitle: state.realTimeMode.value,
isHaveLine: false, // isHaveLine: false,
isHaveDirection: true, // isHaveDirection: true,
isHaveRightWidget: false, // isHaveRightWidget: false,
action: () { // action: () {
Navigator.pushNamed(context, Routers.liveVideoPage, // Navigator.pushNamed(context, Routers.liveVideoPage,
arguments: { // arguments: {
'lockSetInfoData': state.lockSetInfoData.value, // 'lockSetInfoData': state.lockSetInfoData.value,
'catEyeConfigData': state.lockSetInfoData.value // 'catEyeConfigData': state.lockSetInfoData.value
.lockSettingInfo!.catEyeConfig!.isNotEmpty // .lockSettingInfo!.catEyeConfig!.isNotEmpty
? state.lockSetInfoData.value.lockSettingInfo! // ? state.lockSetInfoData.value.lockSettingInfo!
.catEyeConfig![0] // .catEyeConfig![0]
: null // : null
}); // });
}, // },
), // ),
) // )
], ],
), ),
), ),

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/blue/blue_manage.dart'; import 'package:star_lock/blue/blue_manage.dart';
import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsWithParameters.dart'; import 'package:star_lock/blue/io_protocol/io_setSupportFunctionsWithParameters.dart';
import 'package:star_lock/blue/io_reply.dart'; import 'package:star_lock/blue/io_reply.dart';
@ -82,30 +83,31 @@ class CatEyeSetLogic extends BaseGetXController {
// //
cancelBlueConnetctToastTimer(); cancelBlueConnetctToastTimer();
dismissEasyLoading(); dismissEasyLoading();
AppLog.log('state.settingOptions.value:${state.settingOptions.value}');
switch (state.settingOptions.value) { switch (state.settingOptions.value) {
case 1: // case 1: //
{ {
updateAutoLightScreenConfig(); await updateAutoLightScreenConfig();
} }
break; break;
case 2: // case 2: //
{ {
updateStayWarnConfig(); await updateStayWarnConfig();
} }
break; break;
case 3: // case 3: //
{ {
updateAbnormalWarnConfig(); await updateAbnormalWarnConfig();
} }
break; break;
case 4: // case 4: //
{ {
updateLightScreenTimeConfig(); await updateLightScreenTimeConfig();
} }
break; break;
case 5: // case 5: //
{ {
updateCatEyeModeConfig(); await updateCatEyeModeConfig();
} }
break; break;
default: default:
@ -288,6 +290,7 @@ class CatEyeSetLogic extends BaseGetXController {
.catEyeConfig![0] .catEyeConfig![0]
.catEyeModeConfig .catEyeModeConfig
?.realTimeMode = state.catEyeConfig.value.realTimeMode; ?.realTimeMode = state.catEyeConfig.value.realTimeMode;
eventBus eventBus
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value)); .fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
} }
@ -456,6 +459,10 @@ class CatEyeSetLogic extends BaseGetXController {
} }
void sendBlueMessage() { void sendBlueMessage() {
showEasyLoading();
showBlueConnetctToastTimer(action: () {
dismissEasyLoading();
});
final message = _buildCatEyeSetBlueMessage(); final message = _buildCatEyeSetBlueMessage();
BlueManage().blueSendData(BlueManage().connectDeviceName, BlueManage().blueSendData(BlueManage().connectDeviceName,
(BluetoothConnectionState connectionState) async { (BluetoothConnectionState connectionState) async {

View File

@ -80,12 +80,12 @@ class _CatEyeSetPageState extends State<CatEyeSetPage> {
isHaveRightWidget: true, isHaveRightWidget: true,
rightWidget: _otherToDoSwitch(2), rightWidget: _otherToDoSwitch(2),
)), )),
Obx(() => CommonItem( // Obx(() => CommonItem(
leftTitel: '异常警告'.tr, // leftTitel: '异常警告'.tr,
rightTitle: '', // rightTitle: '',
isHaveLine: true, // isHaveLine: true,
isHaveRightWidget: true, // isHaveRightWidget: true,
rightWidget: _otherToDoSwitch(3))), // rightWidget: _otherToDoSwitch(3))),
//ToDo //ToDo
CommonItem( CommonItem(
leftTitel: '呼叫目标'.tr, leftTitel: '呼叫目标'.tr,

View File

@ -12,6 +12,8 @@ import 'package:star_lock/blue/blue_manage.dart';
import 'package:star_lock/blue/io_protocol/io_getDeviceModel.dart'; import 'package:star_lock/blue/io_protocol/io_getDeviceModel.dart';
import 'package:star_lock/blue/io_protocol/io_otaUpgrade.dart'; import 'package:star_lock/blue/io_protocol/io_otaUpgrade.dart';
import 'package:star_lock/blue/io_protocol/io_processOtaUpgrade.dart'; import 'package:star_lock/blue/io_protocol/io_processOtaUpgrade.dart';
import 'package:star_lock/blue/io_protocol/io_readVoicePackageFinalResult.dart';
import 'package:star_lock/blue/io_protocol/io_setVoicePackageFinalResult.dart';
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart'; import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart';
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart'; import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart';
import 'package:star_lock/blue/io_reply.dart'; import 'package:star_lock/blue/io_reply.dart';
@ -52,9 +54,14 @@ class SpeechLanguageSettingsLogic extends BaseGetXController {
_handlerVoicePackageConfigureProcess(reply); _handlerVoicePackageConfigureProcess(reply);
} else if (reply is VoicePackageConfigureConfirmationReply) { } else if (reply is VoicePackageConfigureConfirmationReply) {
handleVoiceConfigureThrottled(reply); handleVoiceConfigureThrottled(reply);
} else if (reply is SetVoicePackageFinalResultReply) {
handleSetResult(reply);
} else if (reply is ReadLockCurrentVoicePacketReply) {
handleLockCurrentVoicePacketResult(reply);
} }
}); });
await initList(); await initList();
readLockLanguage();
} }
/// ///
@ -93,7 +100,7 @@ class SpeechLanguageSettingsLogic extends BaseGetXController {
final passthroughItem = PassthroughItem( final passthroughItem = PassthroughItem(
lang: element.lang, lang: element.lang,
timbres: element.timbres, timbres: element.timbres,
langText: '简体中文'.tr + '(中国台湾)'.tr, langText: '简体中文'.tr + '(中国台湾)'.tr + 'Simplified Chinese TW',
name: element.name, name: element.name,
); );
state.languages.add(passthroughItem); state.languages.add(passthroughItem);
@ -432,12 +439,11 @@ class SpeechLanguageSettingsLogic extends BaseGetXController {
_handlerVoicePackageConfigureConfirmation( _handlerVoicePackageConfigureConfirmation(
VoicePackageConfigureConfirmationReply reply, VoicePackageConfigureConfirmationReply reply,
) async { ) async {
final int status = reply.data[2]; showEasyLoading();
switch (status) { showBlueConnetctToastTimer(action: () {
case 0x00: dismissEasyLoading();
cancelBlueConnetctToastTimer(); });
final LoginEntity entity = final LoginEntity entity = await ApiRepository.to.settingCurrentVoiceTimbre(
await ApiRepository.to.settingCurrentVoiceTimbre(
data: { data: {
'lang': state.tempLangStr.value, 'lang': state.tempLangStr.value,
'timbre': state.tempTimbreStr.value, 'timbre': state.tempTimbreStr.value,
@ -445,15 +451,38 @@ class SpeechLanguageSettingsLogic extends BaseGetXController {
lockId: state.lockSetInfoData.value.lockId!, lockId: state.lockSetInfoData.value.lockId!,
); );
if (entity.errorCode!.codeIsSuccessful) { if (entity.errorCode!.codeIsSuccessful) {
showSuccess('设置成功'.tr, something: () { showSuccess('设置成功'.tr, something: () async {
state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.lang =
?.lang = state.tempLangStr.value; state.tempLangStr.value;
state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre
?.timbre = state.tempTimbreStr.value; ?.timbre = state.tempTimbreStr.value;
eventBus.fire(
PassCurrentLockInformationEvent(state.lockSetInfoData.value)); await BlueManage().blueSendData(BlueManage().connectDeviceName,
(BluetoothConnectionState deviceConnectionState) async {
if (deviceConnectionState == BluetoothConnectionState.connected) {
await BlueManage().writeCharacteristicWithResponse(
SetVoicePackageFinalResult(
lockID: BlueManage().connectDeviceName,
languageCode: state.tempLangStr.value,
).packageData(),
);
} else if (deviceConnectionState ==
BluetoothConnectionState.disconnected) {
dismissEasyLoading();
cancelBlueConnetctToastTimer();
showBlueConnetctToast();
}
});
await Future.delayed(Duration(seconds: 1));
}); });
} }
}
void handleSetResult(SetVoicePackageFinalResultReply reply) async {
final int status = reply.data[2];
switch (status) {
case 0x00:
cancelBlueConnetctToastTimer();
dismissEasyLoading(); dismissEasyLoading();
break; break;
default: default:
@ -461,4 +490,74 @@ class SpeechLanguageSettingsLogic extends BaseGetXController {
break; break;
} }
} }
void handleLockCurrentVoicePacketResult(
ReadLockCurrentVoicePacketReply reply) {
final int status = reply.data[2];
switch (status) {
case 0x00:
//
cancelBlueConnetctToastTimer();
const int languageCodeStartIndex = 3;
const int languageCodeLength = 20;
const int languageCodeEndIndex =
languageCodeStartIndex + languageCodeLength; // 23
if (reply.data.length < languageCodeEndIndex) {
throw Exception(
'Reply data is too short to contain LanguageCode. Expected at least $languageCodeEndIndex bytes, got ${reply.data.length}');
}
List<int> languageCodeBytes =
reply.data.sublist(languageCodeStartIndex, languageCodeEndIndex);
String languageCode = String.fromCharCodes(languageCodeBytes);
languageCode = languageCode.trim(); //
languageCode =
languageCode.replaceAll('\u0000', ''); // (null bytes)
if (languageCode != null && languageCode != '') {
final indexWhere = state.languages
.indexWhere((element) => element.lang == languageCode);
if (indexWhere != -1) {
print('锁板上的语言是:$languageCode,下标是:$indexWhere');
state.selectPassthroughListIndex.value = indexWhere;
}
}
dismissEasyLoading();
break;
case 0x06:
//
final List<int> token = reply.data.sublist(2, 6);
if (state.data != null) {
sendFileToDevice(state.data!, token);
}
break;
default:
break;
}
}
void readLockLanguage() async {
showEasyLoading();
showBlueConnetctToastTimer(action: () {
dismissEasyLoading();
});
await BlueManage().blueSendData(BlueManage().connectDeviceName,
(BluetoothConnectionState deviceConnectionState) async {
if (deviceConnectionState == BluetoothConnectionState.connected) {
await BlueManage().writeCharacteristicWithResponse(
ReadLockCurrentVoicePacket(
lockID: BlueManage().connectDeviceName,
).packageData(),
);
} else if (deviceConnectionState ==
BluetoothConnectionState.disconnected) {
dismissEasyLoading();
cancelBlueConnetctToastTimer();
showBlueConnetctToast();
}
});
}
} }

View File

@ -63,6 +63,12 @@ class _SpeechLanguageSettingsPageState
final soundType = state.soundTypeList.value[index]; final soundType = state.soundTypeList.value[index];
return CommonItem( return CommonItem(
leftTitel: soundType, leftTitel: soundType,
leftTitleStyle: TextStyle(
fontSize: 20.sp,
fontWeight: state.selectSoundTypeIndex.value == index
? FontWeight.bold
: null,
),
rightTitle: '', rightTitle: '',
isHaveLine: !isLastItem, isHaveLine: !isLastItem,
isHaveDirection: false, isHaveDirection: false,
@ -94,7 +100,8 @@ class _SpeechLanguageSettingsPageState
height: 8.h, height: 8.h,
), ),
// //
Container( Obx(
() => Container(
color: Colors.transparent, color: Colors.transparent,
child: Column( child: Column(
children: List.generate( children: List.generate(
@ -103,10 +110,18 @@ class _SpeechLanguageSettingsPageState
final item = state.languages[index]; final item = state.languages[index];
return CommonItem( return CommonItem(
leftTitel: item.langText, leftTitel: item.langText,
leftTitleStyle: TextStyle(
fontSize: 20.sp,
fontWeight: state.selectPassthroughListIndex.value == index
? FontWeight.bold
: null,
),
rightTitle: '', rightTitle: '',
isHaveLine: true, isHaveLine: true,
isHaveDirection: false, isHaveDirection: false,
isHaveRightWidget: true, isHaveRightWidget: true,
leftTitleMaxWidth: 0.9.sw,
//
rightWidget: rightWidget:
state.selectPassthroughListIndex.value == index state.selectPassthroughListIndex.value == index
? Image( ? Image(
@ -125,66 +140,6 @@ class _SpeechLanguageSettingsPageState
), ),
), ),
), ),
],
),
),
);
}
Widget _buildBody() {
return Obx(
() => SingleChildScrollView(
child: Column(
children: [
ListView.builder(
itemCount: state.soundTypeList.length,
itemBuilder: (BuildContext context, int index) {
// itemCount - 1
final isLastItem = index == state.soundTypeList.length - 1;
// platFormSet RxList<Platform>
final platform = state.soundTypeList.value[index];
return CommonItem(
leftTitel: state.soundTypeList.value[index],
rightTitle: '',
isHaveLine: !isLastItem,
// 线
isHaveDirection: false,
isHaveRightWidget: true,
rightWidget: Radio<String>(
// Radio 使 id
value: platform,
// selectPlatFormIndex id
groupValue: state.soundTypeList
.value[state.selectSoundTypeIndex.value],
//
activeColor: AppColors.mainColor,
// Radio
onChanged: (value) {
if (value != null) {
setState(() {
// id
final newIndex = state.soundTypeList.value
.indexWhere((p) => p == value);
if (newIndex != -1) {
state.selectSoundTypeIndex.value = newIndex;
}
});
}
},
),
action: () {
setState(() {
state.selectSoundTypeIndex.value = index;
});
},
);
},
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics() //add this line,
),
Column(
children: _buildList(),
), ),
], ],
), ),
@ -192,16 +147,7 @@ class _SpeechLanguageSettingsPageState
); );
} }
List<Widget> _buildList() {
final appLocalLanguages = state.languages;
return List.generate(
appLocalLanguages.length,
(index) => _buildItem(
appLocalLanguages[index],
index,
),
);
}
@override @override
void dispose() { void dispose() {
@ -211,24 +157,4 @@ class _SpeechLanguageSettingsPageState
} }
} }
_buildItem(PassthroughItem item, index) {
return CommonItem(
leftTitel: item.langText,
rightTitle: '',
isHaveLine: true,
isHaveDirection: false,
isHaveRightWidget: true,
rightWidget: state.selectPassthroughListIndex.value == index
? Image(
image: const AssetImage('images/icon_item_checked.png'),
width: 30.w,
height: 30.w,
fit: BoxFit.contain,
)
: Container(),
action: () {
state.selectPassthroughListIndex.value = index;
},
);
}
} }

View File

@ -16,6 +16,7 @@ class ThirdPartyPlatformState {
final RxList<String> platFormSet = List.of({ final RxList<String> platFormSet = List.of({
'锁通通'.tr, '锁通通'.tr,
'涂鸦智能'.tr, '涂鸦智能'.tr,
'Matter'.tr ,
}).obs; }).obs;
RxInt selectPlatFormIndex = 0.obs; RxInt selectPlatFormIndex = 0.obs;

View File

@ -145,9 +145,9 @@ class EditVideoLogLogic extends BaseGetXController {
} }
// URL生成唯一的文件名MD5哈希值 // URL生成唯一的文件名MD5哈希值
String getFileNameFromUrl(String url, String extension) { String getFileNameFromUrl(String url, String extension, int recordType) {
final hash = md5.convert(utf8.encode(url)).toString(); // 使 md5 final hash = md5.convert(utf8.encode(url)).toString(); // 使 md5
return '$hash.$extension'; return '$recordType' + '_' + '$hash.$extension';
} }
Future<void> recordDownloadTime(String filePath) async { Future<void> recordDownloadTime(String filePath) async {
@ -169,7 +169,7 @@ class EditVideoLogLogic extends BaseGetXController {
} }
// //
Future<String?> downloadFile(String? url) async { Future<String?> downloadFile(String? url, int recordType) async {
if (url == null || url.isEmpty) { if (url == null || url.isEmpty) {
print('URL不能为空'); print('URL不能为空');
return null; return null;
@ -183,7 +183,8 @@ class EditVideoLogLogic extends BaseGetXController {
// URL生成唯一文件名 // URL生成唯一文件名
String extension = _getFileTypeFromUrl(url); // String extension = _getFileTypeFromUrl(url); //
String fileName = getFileNameFromUrl(url, extension); // URL生成唯一文件名 String fileName =
getFileNameFromUrl(url, extension, recordType); // URL生成唯一文件名
String savePath = '${appDocDir.path}/downloads/$fileName'; // String savePath = '${appDocDir.path}/downloads/$fileName'; //
// //

View File

@ -76,23 +76,31 @@ class _EditVideoLogPageState extends State<EditVideoLogPage> {
body: Column( body: Column(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: Obx(() => ListView.builder( child: Obx(
() => ListView.builder(
itemCount: state.videoLogList.length, itemCount: state.videoLogList.length,
itemBuilder: (BuildContext c, int index) { itemBuilder: (BuildContext c, int index) {
final CloudStorageData item = state.videoLogList[index]; final CloudStorageData item = state.videoLogList[index];
return Column( return ExpansionTile(
children: <Widget>[ shape: Border(),
Container( collapsedShape: Border(),
margin: EdgeInsets.only( expansionAnimationStyle: AnimationStyle(
left: 20.w, top: 15.w, bottom: 15.w), curve: Curves.easeInOut,
child: Row(children: <Widget>[ duration: Duration(milliseconds: 400),
Text(item.date ?? '', ),
style: TextStyle(fontSize: 20.sp)), initiallyExpanded: true,
])), title: Text(
mainListView(index, item) item.date ?? '',
], style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w600,
),
),
children: mainListView(index, item),
); );
})), },
),
),
), ),
bottomBottomBtnWidget() bottomBottomBtnWidget()
], ],
@ -100,29 +108,35 @@ class _EditVideoLogPageState extends State<EditVideoLogPage> {
); );
} }
Widget _buildNotData() {
return Expanded(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
'images/icon_noData.png',
width: 160.w,
height: 180.h,
),
Text(
'暂无数据'.tr,
style: TextStyle(
color: AppColors.darkGrayTextColor, fontSize: 22.sp),
)
],
),
),
);
}
double itemW = (1.sw - 15.w * 4) / 3; double itemW = (1.sw - 15.w * 4) / 3;
double itemH = (1.sw - 15.w * 4) / 3 + 40.h; double itemH = (1.sw - 15.w * 4) / 3 + 40.h;
Widget mainListView(int index, CloudStorageData itemData) { //
return GridView.builder( List<Widget> mainListView(int index, CloudStorageData itemData) {
padding: EdgeInsets.only(left: 15.w, right: 15.w), return itemData.recordList!.map((e) => videoItem(e)).toList();
itemCount: itemData.recordList!.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
//
crossAxisCount: 3,
//
mainAxisSpacing: 10.w,
//
crossAxisSpacing: 15.w,
//
childAspectRatio: itemW / itemH),
itemBuilder: (BuildContext context, int index) {
final RecordListData recordData = itemData.recordList![index];
return videoItem(recordData);
},
);
} }
// Widget videoItem(RecordListData recordData, int index) { // Widget videoItem(RecordListData recordData, int index) {
@ -237,9 +251,9 @@ class _EditVideoLogPageState extends State<EditVideoLogPage> {
if (state.selectVideoLogList.value.isNotEmpty) { if (state.selectVideoLogList.value.isNotEmpty) {
state.selectVideoLogList.value.forEach((element) { state.selectVideoLogList.value.forEach((element) {
if (element.videoUrl != null && element.videoUrl != '') { if (element.videoUrl != null && element.videoUrl != '') {
logic.downloadFile(element.videoUrl ?? ''); logic.downloadFile(element.videoUrl ?? '', element.recordType!);
} else if (element.imagesUrl != null && element.imagesUrl != '') { } else if (element.imagesUrl != null && element.imagesUrl != '') {
logic.downloadFile(element.imagesUrl ?? ''); logic.downloadFile(element.imagesUrl ?? '', element.recordType!);
} }
}); });
// double _progress = 0.0; // double _progress = 0.0;
@ -338,55 +352,6 @@ class _EditVideoLogPageState extends State<EditVideoLogPage> {
Widget videoItem(RecordListData recordData) { Widget videoItem(RecordListData recordData) {
return GestureDetector( return GestureDetector(
onTap: () {
if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) {
Get.toNamed(Routers.videoLogDetailPage, arguments: <String, Object>{
'recordData': recordData,
'videoDataList': state.videoLogList.value
});
} else if (recordData.imagesUrl != null &&
recordData.imagesUrl!.isNotEmpty) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FullScreenImagePage(
imageUrl: recordData.imagesUrl!,
),
),
);
}
},
child: Stack(
children: [
SizedBox(
width: itemW,
height: itemH,
child: Column(
children: <Widget>[
Container(
width: itemW,
height: itemW,
margin: const EdgeInsets.all(0),
color: Colors.white,
child: ClipRRect(
borderRadius: BorderRadius.circular(10.w),
child: _buildImageOrVideoItem(recordData),
),
),
SizedBox(height: 5.h),
Text(
DateTool()
.dateToYMDHNString(recordData.operateDate.toString()),
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18.sp),
)
],
),
),
Positioned(
top: 0.w,
right: 0.w,
child: GestureDetector(
onTap: () { onTap: () {
recordData.isSelect = !recordData.isSelect!; recordData.isSelect = !recordData.isSelect!;
if (recordData.isSelect! == true) { if (recordData.isSelect! == true) {
@ -396,20 +361,146 @@ class _EditVideoLogPageState extends State<EditVideoLogPage> {
} }
setState(() {}); setState(() {});
}, },
child: Image( child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.w),
margin: EdgeInsets.only(
bottom: 20.h,
left: 18.w,
right: 18.w,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10.w),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(0, 3), // changes position of shadow
),
],
),
width: 1.sw,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: [
Image(
width: 36.w, width: 36.w,
height: 36.w, height: 36.w,
image: state.selectVideoLogList.value.contains(recordData) image: state.selectVideoLogList.value.contains(recordData)
? const AssetImage('images/icon_round_select.png') ? const AssetImage('images/icon_round_select.png')
: const AssetImage('images/icon_round_unSelect.png'), : const AssetImage('images/icon_round_unSelect.png'),
), ),
SizedBox(
width: 14.w,
),
Container(
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(58.w),
color: AppColors.mainColor,
),
child: Icon(
_buildIconByType(recordData),
size: 48.sp,
color: Colors.white,
),
),
SizedBox(
width: 14.w,
),
Container(
height: itemW,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_buildTitleByType(recordData),
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w600,
),
),
SizedBox(
height: 8.h,
),
Text(
DateTool()
.dateToHNString(recordData.operateDate.toString()),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.w600,
),
), ),
)
], ],
), ),
),
],
),
Container(
width: 118.w,
height: 118.w,
margin: const EdgeInsets.all(0),
color: Colors.white,
child: ClipRRect(
borderRadius: BorderRadius.circular(10.w),
child: _buildImageOrVideoItem(recordData),
),
),
],
),
),
); );
} }
String _buildTitleByType(RecordListData item) {
final recordType = item.recordType;
switch (recordType) {
case 130:
return '防拆报警'.tr;
case 160:
return '人脸'.tr + '开锁'.tr;
case 220:
return '逗留警告'.tr;
default:
return '';
}
}
IconData _buildIconByType(RecordListData item) {
final recordType = item.recordType;
switch (recordType) {
case 130:
return Icons.fmd_bad_outlined;
case 160:
return Icons.tag_faces_outlined;
case 220:
return Icons.wifi_tethering_error_rounded_outlined;
default:
return Icons.priority_high_rounded;
}
}
Color _buildTextColorByType(RecordListData item) {
final recordType = item.recordType;
switch (recordType) {
case 120:
case 150:
case 130:
case 190:
case 200:
case 210:
case 220:
return Colors.red;
default:
return Colors.black;
}
}
_buildImageOrVideoItem(RecordListData recordData) { _buildImageOrVideoItem(RecordListData recordData) {
if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) { if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) {
return _buildVideoItem(recordData); return _buildVideoItem(recordData);

View File

@ -1,7 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:star_lock/appRouters.dart'; import 'package:star_lock/appRouters.dart';
@ -63,8 +63,17 @@ class VideoLogLogic extends BaseGetXController {
final content = await File(logFilePath).readAsString(); final content = await File(logFilePath).readAsString();
final logData = Map<String, int>.from(json.decode(content)); final logData = Map<String, int>.from(json.decode(content));
//
logData.forEach((filePath, timestamp) { logData.forEach((filePath, timestamp) {
String fileName = filePath
.split('/')
.last; // : 220_f5e371111918ff70cb3532bec20e38c4.mp4
String withoutExt = fileName.replaceAll('.mp4', ''); // 使 substring
String numberStr = withoutExt.split('_').first; // : 220
int number = int.parse(numberStr);
print(number); // : 220
final downloadDateTime = DateTime.fromMillisecondsSinceEpoch(timestamp); final downloadDateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
final dateKey = final dateKey =
'${downloadDateTime.year}-${downloadDateTime.month.toString().padLeft(2, '0')}-${downloadDateTime.day.toString().padLeft(2, '0')}'; '${downloadDateTime.year}-${downloadDateTime.month.toString().padLeft(2, '0')}-${downloadDateTime.day.toString().padLeft(2, '0')}';
@ -77,11 +86,15 @@ class VideoLogLogic extends BaseGetXController {
// //
if (filePath.endsWith('.jpg')) { if (filePath.endsWith('.jpg')) {
groupedDownloads[dateKey]?.add( groupedDownloads[dateKey]?.add(
RecordListData(operateDate: timestamp, imagesUrl: filePath), RecordListData(
operateDate: timestamp,
imagesUrl: filePath,
recordType: number),
); );
} else if (filePath.endsWith('.mp4')) { } else if (filePath.endsWith('.mp4')) {
groupedDownloads[dateKey]?.add( groupedDownloads[dateKey]?.add(
RecordListData(operateDate: timestamp, videoUrl: filePath), RecordListData(
operateDate: timestamp, videoUrl: filePath, recordType: number),
); );
} }
}); });

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:star_lock/appRouters.dart'; import 'package:star_lock/appRouters.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/flavors.dart'; import 'package:star_lock/flavors.dart';
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart';
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_state.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_state.dart';
@ -26,9 +27,7 @@ class VideoLogPage extends StatefulWidget {
class _VideoLogPageState extends State<VideoLogPage> { class _VideoLogPageState extends State<VideoLogPage> {
final VideoLogLogic logic = Get.put(VideoLogLogic()); final VideoLogLogic logic = Get.put(VideoLogLogic());
final VideoLogState state = Get final VideoLogState state = Get.find<VideoLogLogic>().state;
.find<VideoLogLogic>()
.state;
@override @override
void initState() { void initState() {
@ -56,8 +55,7 @@ class _VideoLogPageState extends State<VideoLogPage> {
// title加编辑按钮 // title加编辑按钮
editVideoTip(), editVideoTip(),
Obx( Obx(
() => () => Visibility(
Visibility(
visible: !state.isNavLocal.value, visible: !state.isNavLocal.value,
child: state.videoLogList.length > 0 child: state.videoLogList.length > 0
? Expanded( ? Expanded(
@ -66,17 +64,22 @@ class _VideoLogPageState extends State<VideoLogPage> {
itemBuilder: (BuildContext c, int index) { itemBuilder: (BuildContext c, int index) {
final CloudStorageData item = final CloudStorageData item =
state.videoLogList[index]; state.videoLogList[index];
return Column( return ExpansionTile(
children: <Widget>[ shape: Border(),
Container( collapsedShape: Border(),
margin: EdgeInsets.only( expansionAnimationStyle: AnimationStyle(
left: 20.w, top: 15.w, bottom: 15.w), curve: Curves.easeInOut,
child: Row(children: <Widget>[ duration: Duration(milliseconds: 400),
Text(item.date ?? '', ),
style: TextStyle(fontSize: 20.sp)), initiallyExpanded: true,
])), title: Text(
mainListView(index, item) item.date ?? '',
], style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w600,
),
),
children: mainListView(index, item),
); );
}, },
), ),
@ -86,8 +89,7 @@ class _VideoLogPageState extends State<VideoLogPage> {
), ),
// //
Obx( Obx(
() => () => Visibility(
Visibility(
visible: state.isNavLocal.value, visible: state.isNavLocal.value,
child: state.lockVideoList.length > 0 child: state.lockVideoList.length > 0
? Expanded( ? Expanded(
@ -96,20 +98,22 @@ class _VideoLogPageState extends State<VideoLogPage> {
itemBuilder: (BuildContext c, int index) { itemBuilder: (BuildContext c, int index) {
final CloudStorageData item = final CloudStorageData item =
state.lockVideoList[index]; state.lockVideoList[index];
return Column( return ExpansionTile(
children: <Widget>[ shape: Border(),
Container( collapsedShape: Border(),
margin: EdgeInsets.only( expansionAnimationStyle: AnimationStyle(
left: 20.w, top: 15.w, bottom: 15.w), curve: Curves.easeInOut,
child: Row( duration: Duration(milliseconds: 400),
children: <Widget>[ ),
Text(item.date ?? '', initiallyExpanded: true,
style: TextStyle(fontSize: 20.sp)), title: Text(
], item.date ?? '',
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w600,
), ),
), ),
lockMainListView(index, item) children: mainListView(index, item),
],
); );
}, },
), ),
@ -161,8 +165,9 @@ class _VideoLogPageState extends State<VideoLogPage> {
// logic.clearDownloads(); // logic.clearDownloads();
}); });
}, },
child: Obx(() => child: Obx(
Text('云存'.tr, () => Text(
'云存'.tr,
style: state.isNavLocal.value == true style: state.isNavLocal.value == true
? TextStyle( ? TextStyle(
color: Colors.grey, color: Colors.grey,
@ -171,7 +176,10 @@ class _VideoLogPageState extends State<VideoLogPage> {
: TextStyle( : TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 28.sp, fontSize: 28.sp,
fontWeight: FontWeight.w600)))), fontWeight: FontWeight.w600),
),
),
),
TextButton( TextButton(
onPressed: () { onPressed: () {
setState(() { setState(() {
@ -180,8 +188,7 @@ class _VideoLogPageState extends State<VideoLogPage> {
}); });
}, },
child: Obx( child: Obx(
() => () => Text(
Text(
'已下载'.tr, '已下载'.tr,
style: state.isNavLocal.value == true style: state.isNavLocal.value == true
? TextStyle( ? TextStyle(
@ -215,10 +222,12 @@ class _VideoLogPageState extends State<VideoLogPage> {
EdgeInsets.only(left: 20.w, top: 20.w, bottom: 20.w, right: 10.w), EdgeInsets.only(left: 20.w, top: 20.w, bottom: 20.w, right: 10.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF6F7F8), color: const Color(0xFFF6F7F8),
borderRadius: BorderRadius.circular(20.h)), borderRadius: BorderRadius.circular(
20.h,
),
),
child: Obx( child: Obx(
() => () => Column(
Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
@ -227,15 +236,11 @@ class _VideoLogPageState extends State<VideoLogPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text('3天滚动储存'.tr, Text('3天滚动储存'.tr, style: TextStyle(fontSize: 24.sp)),
style: TextStyle(fontSize: 24.sp)),
SizedBox(height: 10.h), SizedBox(height: 10.h),
Text("${F Text("${F.navTitle}${"已为本设备免费提供3大滚动视频储存服务".tr}",
.navTitle}${"已为本设备免费提供3大滚动视频储存服务"
.tr}",
style: style:
TextStyle(fontSize: 22.sp, color: Colors TextStyle(fontSize: 22.sp, color: Colors.grey)),
.grey)),
], ],
)), )),
SizedBox(width: 15.w), SizedBox(width: 15.w),
@ -243,8 +248,7 @@ class _VideoLogPageState extends State<VideoLogPage> {
Image( Image(
width: 40.w, width: 40.w,
height: 24.w, height: 24.w,
image: const AssetImage( image: const AssetImage('images/icon_right_black.png'))
'images/icon_right_black.png'))
], ],
), ),
SizedBox( SizedBox(
@ -263,8 +267,7 @@ class _VideoLogPageState extends State<VideoLogPage> {
visible: state.validityPeriodInfo.value != null && visible: state.validityPeriodInfo.value != null &&
state.validityPeriodInfo.value?.status == 1, state.validityPeriodInfo.value?.status == 1,
child: Text( child: Text(
'过期时间:${state.validityPeriodInfo.value '过期时间:${state.validityPeriodInfo.value?.validityPeriodEnd}',
?.validityPeriodEnd}',
style: TextStyle( style: TextStyle(
fontSize: 24.sp, fontSize: 24.sp,
), ),
@ -290,8 +293,7 @@ class _VideoLogPageState extends State<VideoLogPage> {
visible: state.validityPeriodInfo.value != null && visible: state.validityPeriodInfo.value != null &&
state.validityPeriodInfo.value?.status == 1, state.validityPeriodInfo.value?.status == 1,
child: Text( child: Text(
'剩余天数:${state.validityPeriodInfo.value '剩余天数:${state.validityPeriodInfo.value?.remainingDays}',
?.remainingDays} ',
style: TextStyle( style: TextStyle(
fontSize: 24.sp, fontSize: 24.sp,
), ),
@ -412,48 +414,12 @@ class _VideoLogPageState extends State<VideoLogPage> {
double itemH = (1.sw - 15.w * 4) / 3 + 40.h; double itemH = (1.sw - 15.w * 4) / 3 + 40.h;
// //
Widget mainListView(int index, CloudStorageData itemData) { List<Widget> mainListView(int index, CloudStorageData itemData) {
return GridView.builder( return itemData.recordList!.map((e) => videoItem(e)).toList();
padding: EdgeInsets.only(left: 15.w, right: 15.w),
itemCount: itemData.recordList!.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
//
crossAxisCount: 3,
//
mainAxisSpacing: 15.w,
//
crossAxisSpacing: 15.w,
//
childAspectRatio: itemW / itemH),
itemBuilder: (BuildContext context, int index) {
final RecordListData recordData = itemData.recordList![index];
return videoItem(recordData);
},
);
} }
Widget lockMainListView(int index, CloudStorageData itemData) { List<Widget> lockMainListView(int index, CloudStorageData itemData) {
return GridView.builder( return itemData.recordList!.map((e) => videoItem(e)).toList();
padding: EdgeInsets.only(left: 15.w, right: 15.w),
itemCount: itemData.recordList!.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
//
crossAxisCount: 3,
//
mainAxisSpacing: 15.w,
//
crossAxisSpacing: 15.w,
//
childAspectRatio: itemW / itemH),
itemBuilder: (BuildContext context, int index) {
final RecordListData recordData = itemData.recordList![index];
return videoItem(recordData);
},
);
} }
Widget videoItem(RecordListData recordData) { Widget videoItem(RecordListData recordData) {
@ -469,22 +435,86 @@ class _VideoLogPageState extends State<VideoLogPage> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) => FullScreenImagePage(
FullScreenImagePage(
imageUrl: recordData.imagesUrl!, imageUrl: recordData.imagesUrl!,
), ),
), ),
); );
} }
}, },
child: SizedBox( child: Container(
width: itemW, padding: EdgeInsets.symmetric(horizontal: 20.w),
height: itemH, margin: EdgeInsets.only(
child: Column( bottom: 20.h,
left: 18.w,
right: 18.w,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10.w),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(0, 3), // changes position of shadow
),
],
),
width: 1.sw,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
Row(
children: [
Container(
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(58.w),
color: AppColors.mainColor,
),
child: Icon(
_buildIconByType(recordData),
size: 48.sp,
color: Colors.white,
),
),
SizedBox(
width: 14.w,
),
Container( Container(
width: itemW,
height: itemW, height: itemW,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_buildTitleByType(recordData),
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w600,
),
),
SizedBox(
height: 8.h,
),
Text(
DateTool()
.dateToHNString(recordData.operateDate.toString()),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
Container(
width: 118.w,
height: 118.w,
margin: const EdgeInsets.all(0), margin: const EdgeInsets.all(0),
color: Colors.white, color: Colors.white,
child: ClipRRect( child: ClipRRect(
@ -492,12 +522,6 @@ class _VideoLogPageState extends State<VideoLogPage> {
child: _buildImageOrVideoItem(recordData), child: _buildImageOrVideoItem(recordData),
), ),
), ),
SizedBox(height: 5.h),
Text(
DateTool().dateToYMDHNString(recordData.operateDate.toString()),
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18.sp),
)
], ],
), ),
), ),
@ -512,6 +536,50 @@ class _VideoLogPageState extends State<VideoLogPage> {
} }
} }
String _buildTitleByType(RecordListData item) {
final recordType = item.recordType;
switch (recordType) {
case 130:
return '防拆报警'.tr;
case 160:
return '人脸'.tr + '开锁'.tr;
case 220:
return '逗留警告'.tr;
default:
return '';
}
}
IconData _buildIconByType(RecordListData item) {
final recordType = item.recordType;
switch (recordType) {
case 130:
return Icons.fmd_bad_outlined;
case 160:
return Icons.tag_faces_outlined;
case 220:
return Icons.wifi_tethering_error_rounded_outlined;
default:
return Icons.priority_high_rounded;
}
}
Color _buildTextColorByType(RecordListData item) {
final recordType = item.recordType;
switch (recordType) {
case 120:
case 150:
case 130:
case 190:
case 200:
case 210:
case 220:
return Colors.red;
default:
return Colors.black;
}
}
_buildVideoItem(RecordListData recordData) { _buildVideoItem(RecordListData recordData) {
return VideoThumbnailImage(videoUrl: recordData.videoUrl!); return VideoThumbnailImage(videoUrl: recordData.videoUrl!);
} }

View File

@ -1,9 +1,13 @@
import 'dart:io';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:star_lock/appRouters.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart';
import 'package:star_lock/main/lockDetail/videoLog/videoLogDetail/controlsOverlay_page.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLogDetail/controlsOverlay_page.dart';
import 'package:star_lock/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_state.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_state.dart';
@ -30,11 +34,11 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
AppLog.log(
'state.recordData.value.videoUrl!' + state.recordData.value.videoUrl!);
state.videoController = VideoPlayerController.networkUrl( state.videoController =
Uri.parse(state.recordData.value.videoUrl!), createVideoController(state.recordData.value.videoUrl!);
videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
);
state.videoController.addListener(() { state.videoController.addListener(() {
setState(() {}); setState(() {});
@ -47,10 +51,8 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
if (state.videoController != null) { if (state.videoController != null) {
await state.videoController.dispose(); // await state.videoController.dispose(); //
} }
state.videoController = VideoPlayerController.networkUrl( state.videoController =
Uri.parse(videoUrl), createVideoController(state.recordData.value.videoUrl!);
videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
);
// //
await state.videoController.initialize(); await state.videoController.initialize();
@ -60,6 +62,22 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
setState(() {}); setState(() {});
} }
VideoPlayerController createVideoController(String url) {
if (url.startsWith('http://') || url.startsWith('https://')) {
return VideoPlayerController.networkUrl(
Uri.parse(url),
videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
);
} else {
final file = File(
url.startsWith('file://') ? url.replaceFirst('file://', '') : url);
return VideoPlayerController.file(
file,
videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -104,6 +122,7 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
], ],
), ),
), ),
// _buildTitleRow(),
_buildOther(), _buildOther(),
], ],
) )
@ -135,14 +154,79 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
); );
} }
}, },
child: SizedBox( child: Container(
width: itemW, padding: EdgeInsets.symmetric(horizontal: 20.w),
height: itemH, margin: EdgeInsets.only(
child: Column( bottom: 20.h,
left: 18.w,
right: 18.w,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10.w),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(0, 3), // changes position of shadow
),
],
),
width: 1.sw,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
Row(
children: [
Container(
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(58.w),
color: AppColors.mainColor,
),
child: Icon(
_buildIconByType(recordData),
size: 48.sp,
color: Colors.white,
),
),
SizedBox(
width: 14.w,
),
Container( Container(
width: itemW,
height: itemW, height: itemW,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_buildTitleByType(recordData),
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w600,
),
),
SizedBox(
height: 8.h,
),
Text(
DateTool()
.dateToHNString(recordData.operateDate.toString()),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
Container(
width: 118.w,
height: 118.w,
margin: const EdgeInsets.all(0), margin: const EdgeInsets.all(0),
color: Colors.white, color: Colors.white,
child: ClipRRect( child: ClipRRect(
@ -213,11 +297,13 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
margin: EdgeInsets.only(left: 20.w, top: 15.w, bottom: 15.w), margin: EdgeInsets.only(left: 20.w, top: 15.w, bottom: 15.w),
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Text(item.date ?? '', style: TextStyle(fontSize: 20.sp)), Text(item.date ?? '',
style: TextStyle(
fontSize: 24.sp, fontWeight: FontWeight.w600)),
], ],
), ),
), ),
mainListView(index, item), ...mainListView(index, item),
], ],
); );
}, },
@ -225,22 +311,60 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
); );
} }
Widget mainListView(int index, CloudStorageData itemData) { //
return GridView.builder( List<Widget> mainListView(int index, CloudStorageData itemData) {
itemCount: itemData.recordList!.length, return itemData.recordList!.map((e) => videoItem(e)).toList();
shrinkWrap: true, }
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( String _buildTitleByType(RecordListData item) {
// final recordType = item.recordType;
crossAxisCount: 3, switch (recordType) {
), case 130:
itemBuilder: (BuildContext context, int index) { return '防拆报警'.tr;
return _buildItem(itemData.recordList![index]); case 160:
}, return '人脸'.tr + '开锁'.tr;
); case 220:
return '逗留警告'.tr;
default:
return '';
}
}
IconData _buildIconByType(RecordListData item) {
final recordType = item.recordType;
switch (recordType) {
case 130:
return Icons.fmd_bad_outlined;
case 160:
return Icons.tag_faces_outlined;
case 220:
return Icons.wifi_tethering_error_rounded_outlined;
default:
return Icons.priority_high_rounded;
}
} }
_buildItem(itemData) { _buildItem(itemData) {
return videoItem(itemData); return videoItem(itemData);
} }
_buildTitleRow() {
return Container(
decoration: BoxDecoration(
color: Colors.white,
),
padding: EdgeInsets.only(left: 15.w, top: 24.w, bottom: 24.w),
child: Row(
children: [
Text(
_buildTitleByType(state.recordData.value) ?? '',
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w600,
),
),
],
),
);
}
} }

View File

@ -1,53 +1,48 @@
import 'dart:io'; // dart:io 使 File import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:path_provider/path_provider.dart'; // path_provider import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:video_thumbnail/video_thumbnail.dart'; // video_thumbnail import 'package:path_provider/path_provider.dart';
import 'package:video_thumbnail/video_thumbnail.dart';
class VideoThumbnailImage extends StatefulWidget { class VideoThumbnailImage extends StatefulWidget {
final String videoUrl; final String videoUrl;
VideoThumbnailImage({required this.videoUrl}); const VideoThumbnailImage({Key? key, required this.videoUrl})
: super(key: key);
@override @override
_VideoThumbnailState createState() => _VideoThumbnailState(); _VideoThumbnailState createState() => _VideoThumbnailState();
} }
class _VideoThumbnailState extends State<VideoThumbnailImage> { class _VideoThumbnailState extends State<VideoThumbnailImage> {
final Map<String, String> _thumbnailCache = {}; // // 使 static
late Future<String?> _thumbnailFuture; // Future static final Map<String, Future<String?>> _pendingThumbnails = {};
late Future<String?> _thumbnailFuture;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_thumbnailFuture = _generateThumbnail(); // initState Future // URL Future
_thumbnailFuture = _pendingThumbnails.putIfAbsent(widget.videoUrl, () {
return _generateThumbnail(widget.videoUrl);
});
} }
// // per URL
Future<String?> _generateThumbnail() async { Future<String?> _generateThumbnail(String url) async {
try { try {
//
if (_thumbnailCache.containsKey(widget.videoUrl)) {
return _thumbnailCache[widget.videoUrl];
}
//
final tempDir = await getTemporaryDirectory(); final tempDir = await getTemporaryDirectory();
final thumbnailPath = await VideoThumbnail.thumbnailFile( final thumbnail = await VideoThumbnail.thumbnailFile(
video: widget.videoUrl, video: url,
// URL
thumbnailPath: tempDir.path, thumbnailPath: tempDir.path,
//
imageFormat: ImageFormat.JPEG, imageFormat: ImageFormat.JPEG,
//
maxHeight: 200, maxHeight: 200,
// quality: 100,
quality: 100, // (0-100)
); );
return thumbnail;
//
_thumbnailCache[widget.videoUrl] = thumbnailPath!;
return thumbnailPath;
} catch (e) { } catch (e) {
print('Failed to generate thumbnail: $e'); print('Failed to generate thumbnail: $e');
return null; return null;
@ -57,27 +52,25 @@ class _VideoThumbnailState extends State<VideoThumbnailImage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder<String?>( return FutureBuilder<String?>(
future: _thumbnailFuture, // Future future: _thumbnailFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
//
return Center(child: CircularProgressIndicator()); return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError || !snapshot.hasData) { } else if (snapshot.hasError || !snapshot.hasData) {
// return Center(
return Image.asset( child: Image.asset(
'images/icon_unHaveData.png', // 'images/icon_unHaveData.png',
fit: BoxFit.cover, fit: BoxFit.cover,
),
); );
} else { } else {
//
final thumbnailPath = snapshot.data!;
return Stack( return Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: <Widget>[ children: <Widget>[
RotatedBox( RotatedBox(
quarterTurns: -1, quarterTurns: -1,
child: Image.file( child: Image.file(
File(thumbnailPath), // File(snapshot.data!),
width: 200, width: 200,
height: 200, height: 200,
fit: BoxFit.cover, fit: BoxFit.cover,
@ -85,7 +78,7 @@ class _VideoThumbnailState extends State<VideoThumbnailImage> {
), ),
Icon( Icon(
Icons.play_arrow_rounded, Icons.play_arrow_rounded,
size: 80, size: 88.sp,
color: Colors.white.withOpacity(0.8), color: Colors.white.withOpacity(0.8),
), ),
], ],

View File

@ -297,6 +297,11 @@ class LockListInfoItemEntity {
LockListInfoItemEntity copy() { LockListInfoItemEntity copy() {
return LockListInfoItemEntity.fromJson(toJson()); return LockListInfoItemEntity.fromJson(toJson());
} }
@override
String toString() {
return 'LockListInfoItemEntity{keyId: $keyId, lockId: $lockId, lockName: $lockName, lockAlias: $lockAlias, electricQuantity: $electricQuantity, fwVersion: $fwVersion, hwVersion: $hwVersion, keyType: $keyType, passageMode: $passageMode, userType: $userType, startDate: $startDate, endDate: $endDate, weekDays: $weekDays, remoteEnable: $remoteEnable, faceAuthentication: $faceAuthentication, lastFaceValidateTime: $lastFaceValidateTime, nextFaceValidateTime: $nextFaceValidateTime, keyRight: $keyRight, keyStatus: $keyStatus, isLockOwner: $isLockOwner, sendDate: $sendDate, lockUserNo: $lockUserNo, senderUserId: $senderUserId, electricQuantityDate: $electricQuantityDate, electricQuantityStandby: $electricQuantityStandby, isOnlyManageSelf: $isOnlyManageSelf, restoreCount: $restoreCount, model: $model, vendor: $vendor, bluetooth: $bluetooth, lockFeature: $lockFeature, lockSetting: $lockSetting, hasGateway: $hasGateway, appUnlockOnline: $appUnlockOnline, mac: $mac, initUserNo: $initUserNo, updateDate: $updateDate, network: $network}';
}
} }
class NetworkInfo { class NetworkInfo {
@ -323,6 +328,11 @@ class NetworkInfo {
data['isOnline'] = isOnline; data['isOnline'] = isOnline;
return data; return data;
} }
@override
String toString() {
return 'NetworkInfo{peerId: $peerId, wifiName: $wifiName, isOnline: $isOnline}';
}
} }
class Bluetooth { class Bluetooth {
@ -356,6 +366,11 @@ class Bluetooth {
data['signKey'] = signKey; data['signKey'] = signKey;
return data; return data;
} }
@override
String toString() {
return 'Bluetooth{bluetoothDeviceId: $bluetoothDeviceId, bluetoothDeviceName: $bluetoothDeviceName, publicKey: $publicKey, privateKey: $privateKey, signKey: $signKey}';
}
} }
class LockFeature { class LockFeature {
@ -442,6 +457,11 @@ class LockFeature {
data['isMJpeg'] = isMJpeg; data['isMJpeg'] = isMJpeg;
return data; return data;
} }
@override
String toString() {
return 'LockFeature{password: $password, passwordIssue: $passwordIssue, icCard: $icCard, fingerprint: $fingerprint, fingerVein: $fingerVein, palmVein: $palmVein, isSupportIris: $isSupportIris, d3Face: $d3Face, bluetoothRemoteControl: $bluetoothRemoteControl, videoIntercom: $videoIntercom, isSupportCatEye: $isSupportCatEye, isSupportBackupBattery: $isSupportBackupBattery, isNoSupportedBlueBroadcast: $isNoSupportedBlueBroadcast, wifiLockType: $wifiLockType, wifi: $wifi, isH264: $isH264, isH265: $isH265, isMJpeg: $isMJpeg}';
}
} }
class LockSetting { class LockSetting {
@ -486,6 +506,11 @@ class LockSetting {
} }
return data; return data;
} }
@override
String toString() {
return 'LockSetting{attendance: $attendance, appUnlockOnline: $appUnlockOnline, remoteUnlock: $remoteUnlock, catEyeConfig: $catEyeConfig}';
}
} }
// CatEyeConfig // CatEyeConfig

View File

@ -11,6 +11,8 @@ import 'package:star_lock/appRouters.dart';
import 'package:star_lock/app_settings/app_colors.dart'; import 'package:star_lock/app_settings/app_colors.dart';
import 'package:star_lock/blue/blue_manage.dart'; import 'package:star_lock/blue/blue_manage.dart';
import 'package:star_lock/blue/io_protocol/io_getDeviceModel.dart'; import 'package:star_lock/blue/io_protocol/io_getDeviceModel.dart';
import 'package:star_lock/blue/io_protocol/io_readVoicePackageFinalResult.dart';
import 'package:star_lock/blue/io_protocol/io_setVoicePackageFinalResult.dart';
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart'; import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart';
import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart'; import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart';
import 'package:star_lock/blue/io_reply.dart'; import 'package:star_lock/blue/io_reply.dart';
@ -50,9 +52,14 @@ class LockVoiceSettingLogic extends BaseGetXController {
_handlerVoicePackageConfigureProcess(reply); _handlerVoicePackageConfigureProcess(reply);
} else if (reply is VoicePackageConfigureConfirmationReply) { } else if (reply is VoicePackageConfigureConfirmationReply) {
handleVoiceConfigureThrottled(reply); handleVoiceConfigureThrottled(reply);
} else if (reply is ReadLockCurrentVoicePacketReply) {
handleLockCurrentVoicePacketResult(reply);
} else if (reply is SetVoicePackageFinalResultReply) {
handleSetResult(reply);
} }
}); });
initList(); initList();
readLockLanguage();
} }
void handleVoiceConfigureThrottled( void handleVoiceConfigureThrottled(
@ -73,6 +80,10 @@ class LockVoiceSettingLogic extends BaseGetXController {
Future<void> _executeLogic( Future<void> _executeLogic(
VoicePackageConfigureConfirmationReply reply) async { VoicePackageConfigureConfirmationReply reply) async {
showEasyLoading();
showBlueConnetctToastTimer(action: () {
dismissEasyLoading();
});
final LoginEntity entity = await ApiRepository.to.settingCurrentVoiceTimbre( final LoginEntity entity = await ApiRepository.to.settingCurrentVoiceTimbre(
data: { data: {
'lang': state.tempLangStr.value, 'lang': state.tempLangStr.value,
@ -81,18 +92,47 @@ class LockVoiceSettingLogic extends BaseGetXController {
lockId: state.lockSetInfoData.value.lockId!, lockId: state.lockSetInfoData.value.lockId!,
); );
if (entity.errorCode!.codeIsSuccessful) { if (entity.errorCode!.codeIsSuccessful) {
showSuccess('设置成功'.tr, something: () { showSuccess('设置成功'.tr, something: () async {
state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.lang = state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.lang =
state.tempLangStr.value; state.tempLangStr.value;
state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre
?.timbre = state.tempTimbreStr.value; ?.timbre = state.tempTimbreStr.value;
await BlueManage().blueSendData(BlueManage().connectDeviceName,
(BluetoothConnectionState deviceConnectionState) async {
if (deviceConnectionState == BluetoothConnectionState.connected) {
await BlueManage().writeCharacteristicWithResponse(
SetVoicePackageFinalResult(
lockID: BlueManage().connectDeviceName,
languageCode: state.tempLangStr.value,
).packageData(),
);
} else if (deviceConnectionState ==
BluetoothConnectionState.disconnected) {
dismissEasyLoading();
cancelBlueConnetctToastTimer();
showBlueConnetctToast();
}
});
await Future.delayed(Duration(seconds: 1));
eventBus eventBus
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value)); .fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
Get.offAllNamed(Routers.starLockMain); Get.offAllNamed(Routers.starLockMain);
}); });
} }
}
void handleSetResult(SetVoicePackageFinalResultReply reply) async {
final int status = reply.data[2];
switch (status) {
case 0x00:
cancelBlueConnetctToastTimer();
dismissEasyLoading(); dismissEasyLoading();
break;
default:
showToast('设置'.tr + '失败'.tr);
break;
}
} }
void saveSpeechLanguageSettings() async { void saveSpeechLanguageSettings() async {
@ -202,9 +242,7 @@ class LockVoiceSettingLogic extends BaseGetXController {
case 0x00: case 0x00:
// //
cancelBlueConnetctToastTimer(); cancelBlueConnetctToastTimer();
_startSendLanguageFile(); _startSendLanguageFile();
break; break;
case 0x06: case 0x06:
// //
@ -214,7 +252,8 @@ class LockVoiceSettingLogic extends BaseGetXController {
} }
break; break;
default: default:
showToast('获取设备型号失败'.tr); dismissEasyLoading();
cancelBlueConnetctToastTimer();
break; break;
} }
} }
@ -250,11 +289,10 @@ class LockVoiceSettingLogic extends BaseGetXController {
if (lang == 'zh_TW') { if (lang == 'zh_TW') {
// //
List<String> parts = lang.split('_'); List<String> parts = lang.split('_');
final indexOf = locales.indexOf(Locale(parts[0], parts[1]));
final passthroughItem = PassthroughItem( final passthroughItem = PassthroughItem(
lang: element.lang, lang: element.lang,
timbres: element.timbres, timbres: element.timbres,
langText: '简体中文'.tr + '(中国台湾)'.tr, langText: '简体中文'.tr + '(中国台湾)'.tr + 'Simplified Chinese TW',
name: element.name, name: element.name,
); );
state.languages.add(passthroughItem); state.languages.add(passthroughItem);
@ -268,6 +306,7 @@ class LockVoiceSettingLogic extends BaseGetXController {
ExtensionLanguageType.fromLocale(locales[indexOf]).lanTitle, ExtensionLanguageType.fromLocale(locales[indexOf]).lanTitle,
name: element.name, name: element.name,
); );
state.languages.add(passthroughItem); state.languages.add(passthroughItem);
} }
}); });
@ -403,4 +442,77 @@ class LockVoiceSettingLogic extends BaseGetXController {
state.data = null; state.data = null;
super.onClose(); super.onClose();
} }
void readLockLanguage() async {
showEasyLoading();
showBlueConnetctToastTimer(action: () {
dismissEasyLoading();
});
await BlueManage().blueSendData(BlueManage().connectDeviceName,
(BluetoothConnectionState deviceConnectionState) async {
if (deviceConnectionState == BluetoothConnectionState.connected) {
await BlueManage().writeCharacteristicWithResponse(
ReadLockCurrentVoicePacket(
lockID: BlueManage().connectDeviceName,
).packageData(),
);
} else if (deviceConnectionState ==
BluetoothConnectionState.disconnected) {
dismissEasyLoading();
cancelBlueConnetctToastTimer();
showBlueConnetctToast();
}
});
}
void handleLockCurrentVoicePacketResult(
ReadLockCurrentVoicePacketReply reply) {
final int status = reply.data[2];
switch (status) {
case 0x00:
//
cancelBlueConnetctToastTimer();
const int languageCodeStartIndex = 3;
const int languageCodeLength = 20;
const int languageCodeEndIndex =
languageCodeStartIndex + languageCodeLength; // 23
if (reply.data.length < languageCodeEndIndex) {
throw Exception(
'Reply data is too short to contain LanguageCode. Expected at least $languageCodeEndIndex bytes, got ${reply.data.length}');
}
List<int> languageCodeBytes =
reply.data.sublist(languageCodeStartIndex, languageCodeEndIndex);
String languageCode = String.fromCharCodes(languageCodeBytes);
languageCode = languageCode.trim(); //
languageCode =
languageCode.replaceAll('\u0000', ''); // (null bytes)
print('LanguageCode: $languageCode'); // : zh_CN, en_US
if (languageCode != null && languageCode != '') {
final indexWhere = state.languages
.indexWhere((element) => element.lang == languageCode);
if (indexWhere != -1) {
print('锁板上的语言是:$languageCode,下标是:$indexWhere');
state.selectPassthroughListIndex.value = indexWhere;
}
}
dismissEasyLoading();
break;
case 0x06:
//
final List<int> token = reply.data.sublist(2, 6);
if (state.data != null) {
sendFileToDevice(state.data!, token);
}
break;
default:
break;
}
}
} }

View File

@ -95,6 +95,12 @@ class _LockVoiceSettingState extends State<LockVoiceSetting> {
final soundType = state.soundTypeList.value[index]; final soundType = state.soundTypeList.value[index];
return CommonItem( return CommonItem(
leftTitel: soundType, leftTitel: soundType,
leftTitleStyle: TextStyle(
fontSize: 20.sp,
fontWeight: state.selectSoundTypeIndex.value == index
? FontWeight.bold
: null,
),
rightTitle: '', rightTitle: '',
isHaveLine: !isLastItem, isHaveLine: !isLastItem,
isHaveDirection: false, isHaveDirection: false,
@ -135,10 +141,18 @@ class _LockVoiceSettingState extends State<LockVoiceSetting> {
final item = state.languages[index]; final item = state.languages[index];
return CommonItem( return CommonItem(
leftTitel: item.langText, leftTitel: item.langText,
leftTitleStyle: TextStyle(
fontSize: 20.sp,
fontWeight:
state.selectPassthroughListIndex.value == index
? FontWeight.bold
: null,
),
rightTitle: '', rightTitle: '',
isHaveLine: true, isHaveLine: true,
isHaveDirection: false, isHaveDirection: false,
isHaveRightWidget: true, isHaveRightWidget: true,
leftTitleMaxWidth: 0.85.sw,
rightWidget: rightWidget:
state.selectPassthroughListIndex.value == index state.selectPassthroughListIndex.value == index
? Image( ? Image(

View File

@ -8,7 +8,7 @@ import 'messageDetail_state.dart';
class MessageDetailLogic extends BaseGetXController { class MessageDetailLogic extends BaseGetXController {
final MessageDetailState state = MessageDetailState(); final MessageDetailState state = MessageDetailState();
// //
Future<void> readMessageDataRequest() async { Future<void> readMessageDataRequest() async {
final MessageListEntity entity = await ApiRepository.to.readMessageLoadData(messageId:state.itemData.value.id!); final MessageListEntity entity = await ApiRepository.to.readMessageLoadData(messageId:state.itemData.value.id!);
if (entity.errorCode!.codeIsSuccessful) { if (entity.errorCode!.codeIsSuccessful) {

View File

@ -24,14 +24,22 @@ class MessageListEntity {
} }
return data; return data;
} }
@override
String toString() {
return 'MessageListEntity{errorCode: $errorCode, description: $description, errorMsg: $errorMsg, data: $data}';
}
} }
class Data { class Data {
List<MessageItemEntity>? list; List<MessageItemEntity>? list;
int? pageNo; int? pageNo;
int? pageSize; int? pageSize;
int? total;
int? readCount;
int? unreadCount;
Data({this.list, this.pageNo, this.pageSize}); Data({this.list, this.pageNo, this.pageSize, this.total,this.readCount, this.unreadCount});
Data.fromJson(Map<String, dynamic> json) { Data.fromJson(Map<String, dynamic> json) {
if (json['list'] != null) { if (json['list'] != null) {
@ -42,6 +50,9 @@ class Data {
} }
pageNo = json['pageNo']; pageNo = json['pageNo'];
pageSize = json['pageSize']; pageSize = json['pageSize'];
total = json['total'];
readCount = json['readCount'];
unreadCount = json['unreadCount'];
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -51,8 +62,16 @@ class Data {
} }
data['pageNo'] = pageNo; data['pageNo'] = pageNo;
data['pageSize'] = pageSize; data['pageSize'] = pageSize;
data['total'] = total;
data['readCount'] = readCount;
data['unreadCount'] = unreadCount;
return data; return data;
} }
@override
String toString() {
return 'Data{list: $list, pageNo: $pageNo, pageSize: $pageSize, total: $total, readCount: $readCount, unreadCount: $unreadCount}';
}
} }
class MessageItemEntity { class MessageItemEntity {
@ -78,4 +97,9 @@ class MessageItemEntity {
data['readAt'] = readAt; data['readAt'] = readAt;
return data; return data;
} }
@override
String toString() {
return 'MessageItemEntity{id: $id, data: $data, createdAt: $createdAt, readAt: $readAt}';
}
} }

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter_app_badger/flutter_app_badger.dart'; import 'package:flutter_app_badger/flutter_app_badger.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/tools/baseGetXController.dart'; import 'package:star_lock/tools/baseGetXController.dart';
import '../../../network/api_repository.dart'; import '../../../network/api_repository.dart';
import '../../../tools/eventBusEventManage.dart'; import '../../../tools/eventBusEventManage.dart';
@ -18,6 +19,10 @@ class MessageListLogic extends BaseGetXController {
final MessageListEntity entity = await ApiRepository.to final MessageListEntity entity = await ApiRepository.to
.messageListLoadData(pageNo: pageNo.toString(), pageSize: pageSize); .messageListLoadData(pageNo: pageNo.toString(), pageSize: pageSize);
if (entity.errorCode!.codeIsSuccessful) { if (entity.errorCode!.codeIsSuccessful) {
AppLog.log('消息列表数据请求成功:${entity.data!.total}');
//
await FlutterAppBadger.updateBadgeCount(entity.data!.unreadCount!);
if (pageNo == 1) { if (pageNo == 1) {
state.itemDataList.value = entity.data!.list!; state.itemDataList.value = entity.data!.list!;
pageNo++; pageNo++;

View File

@ -104,6 +104,7 @@ class _MineMultiLanguagePageState extends State<MineMultiLanguagePage> {
isHaveLine: true, isHaveLine: true,
isHaveDirection: false, isHaveDirection: false,
isHaveRightWidget: true, isHaveRightWidget: true,
leftTitleMaxWidth: 0.9.sw, //
rightWidget: state.currentLanguageType.value == lanType rightWidget: state.currentLanguageType.value == lanType
? Image( ? Image(
image: const AssetImage('images/icon_item_checked.png'), image: const AssetImage('images/icon_item_checked.png'),

View File

@ -2169,7 +2169,8 @@ class ApiProvider extends BaseProvider {
readMessageURL.toUrl, readMessageURL.toUrl,
jsonEncode({ jsonEncode({
'id': messageId, 'id': messageId,
})); }),
isUnShowLoading: true);
// //
Future<Response> deletMessageLoadData(String messageId) => post( Future<Response> deletMessageLoadData(String messageId) => post(

View File

@ -4,6 +4,7 @@ import 'dart:typed_data';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_pcm_sound/flutter_pcm_sound.dart'; import 'package:flutter_pcm_sound/flutter_pcm_sound.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart'; import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart';
import 'package:star_lock/talk/starChart/constant/message_type_constant.dart'; import 'package:star_lock/talk/starChart/constant/message_type_constant.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart'; import 'package:star_lock/talk/starChart/constant/talk_status.dart';
@ -15,6 +16,7 @@ import 'package:star_lock/talk/starChart/proto/generic.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_accept.pb.dart'; import 'package:star_lock/talk/starChart/proto/talk_accept.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart'; import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart';
import 'package:star_lock/tools/commonDataManage.dart'; import 'package:star_lock/tools/commonDataManage.dart';
import 'package:star_lock/tools/eventBusEventManage.dart';
import 'package:star_lock/tools/storage.dart'; import 'package:star_lock/tools/storage.dart';
import '../../star_chart_manage.dart'; import '../../star_chart_manage.dart';
@ -43,6 +45,25 @@ class UdpTalkAcceptHandler extends ScpMessageBaseHandle
// //
talkeRequestOverTimeTimerManager.renew(); talkeRequestOverTimeTimerManager.renew();
talkeRequestOverTimeTimerManager.cancel(); talkeRequestOverTimeTimerManager.cancel();
//
//
AppLog.log('msg:${scpMessage}');
AppLog.log('msg:${startChartManage.lockListPeerId}');
// id
final fromPeerId = scpMessage.FromPeerId;
if (fromPeerId != null && fromPeerId != '') {
startChartManage.lockListPeerId.forEach((element) {
if (element != null &&
element.network != null &&
element.network!.peerId == fromPeerId) {
//
eventBus.fire(ReadTalkMessageRefreshUI(element.lockName!));
}
});
}
// rbcuInfo数据 // rbcuInfo数据
// startChartManage.startSendingRbcuInfoMessages( // startChartManage.startSendingRbcuInfoMessages(
// ToPeerId: startChartManage.lockPeerId); // ToPeerId: startChartManage.lockPeerId);

View File

@ -1,11 +1,45 @@
import 'dart:async';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:star_lock/mine/message/messageList/messageList_entity.dart';
import 'package:star_lock/network/api_repository.dart';
import 'package:star_lock/network/start_chart_api.dart'; import 'package:star_lock/network/start_chart_api.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart'; import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/star_chart_manage.dart'; import 'package:star_lock/talk/starChart/star_chart_manage.dart';
import 'package:star_lock/tools/baseGetXController.dart';
import 'package:star_lock/tools/eventBusEventManage.dart';
import 'package:star_lock/tools/storage.dart'; import 'package:star_lock/tools/storage.dart';
class AppLifecycleObserver extends WidgetsBindingObserver { class AppLifecycleObserver extends WidgetsBindingObserver {
//
StreamSubscription? _readMessageRefreshUIEvent;
void _readMessageRefreshUIAction() {
// eventBus
_readMessageRefreshUIEvent =
eventBus.on<ReadTalkMessageRefreshUI>().listen((event) async {
//
final MessageListEntity entity = await ApiRepository.to
.messageListLoadData(pageNo: '1', pageSize: '1');
if (entity.errorCode!.codeIsSuccessful) {
final lockName = event.lockName;
if (lockName != null && lockName.isNotEmpty) {
final readAt = entity.data?.list?.first.readAt == 0;
final data = entity.data?.list?.first.data;
if (readAt && data != null && data.contains(lockName)) {
//
final entity2 = await ApiRepository.to
.readMessageLoadData(messageId: entity.data!.list!.first.id!);
if (entity2.errorCode!.codeIsSuccessful) {
eventBus.fire(ReadMessageRefreshUI());
}
}
}
}
});
}
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state); super.didChangeAppLifecycleState(state);
@ -37,6 +71,7 @@ class AppLifecycleObserver extends WidgetsBindingObserver {
Get.back(); Get.back();
} }
StartChartManage().destruction(); StartChartManage().destruction();
_readMessageRefreshUIEvent?.cancel();
} }
void onAppResumed() async { void onAppResumed() async {
@ -52,6 +87,9 @@ class AppLifecycleObserver extends WidgetsBindingObserver {
StartChartApi.to.startChartHost = StartChartApi.to.startChartHost =
loginData!.starchart!.scdUrl ?? StartChartApi.to.startChartHost; loginData!.starchart!.scdUrl ?? StartChartApi.to.startChartHost;
} }
//
_readMessageRefreshUIAction();
print('App has resumed to the foreground.'); print('App has resumed to the foreground.');
} }

View File

@ -110,22 +110,8 @@ class ImageTransmissionLogic extends BaseGetXController {
// //
switch (contentType) { switch (contentType) {
case TalkData_ContentTypeE.G711: case TalkData_ContentTypeE.G711:
// // //
if (_isFirstAudioFrame) { if (!state.isOpenVoice.value && state.isRecordingAudio.value) {
_startAudioTime = currentTime;
_isFirstAudioFrame = false;
}
//
final expectedTime = _startAudioTime + talkData.durationMs;
final audioDelay = currentTime - expectedTime;
//
if (audioDelay > 500) {
state.audioBuffer.clear();
if (state.isOpenVoice.value) {
_playAudioFrames();
}
return; return;
} }
if (state.audioBuffer.length >= audioBufferSize) { if (state.audioBuffer.length >= audioBufferSize) {
@ -212,7 +198,8 @@ class ImageTransmissionLogic extends BaseGetXController {
/// ///
void _playAudioData(TalkData talkData) async { void _playAudioData(TalkData talkData) async {
if (state.isOpenVoice.value) { if (state.isOpenVoice.value &&
state.isRecordingAudio.value == false) {
final list = final list =
G711().decodeAndDenoise(talkData.content, true, 8000, 300, 150); G711().decodeAndDenoise(talkData.content, true, 8000, 300, 150);
// // PCM PcmArrayInt16 // // PCM PcmArrayInt16
@ -561,6 +548,8 @@ class ImageTransmissionLogic extends BaseGetXController {
state.voiceProcessor = VoiceProcessor.instance; state.voiceProcessor = VoiceProcessor.instance;
} }
Timer? _startProcessingAudioTimer;
// //
Future<void> startProcessingAudio() async { Future<void> startProcessingAudio() async {
try { try {
@ -580,7 +569,6 @@ class ImageTransmissionLogic extends BaseGetXController {
} on PlatformException catch (ex) { } on PlatformException catch (ex) {
// state.errorMessage.value = 'Failed to start recorder: $ex'; // state.errorMessage.value = 'Failed to start recorder: $ex';
} }
state.isOpenVoice.value = false;
} }
/// ///
@ -600,47 +588,65 @@ class ImageTransmissionLogic extends BaseGetXController {
} on PlatformException catch (ex) { } on PlatformException catch (ex) {
// state.errorMessage.value = 'Failed to stop recorder: $ex'; // state.errorMessage.value = 'Failed to stop recorder: $ex';
} finally { } finally {
//
if (_startProcessingAudioTimer != null) {
// 53200
for (int i = 0; i < 5; i++) {
_bufferedAudioFrames.addAll(List.filled(chunkSize, 0));
}
Future.delayed(const Duration(milliseconds: 300), () {
_startProcessingAudioTimer?.cancel();
_startProcessingAudioTimer = null;
_bufferedAudioFrames.clear();
});
} else {
_bufferedAudioFrames.clear();
}
final bool? isRecording = await state.voiceProcessor?.isRecording(); final bool? isRecording = await state.voiceProcessor?.isRecording();
state.isRecordingAudio.value = isRecording!; state.isRecordingAudio.value = isRecording!;
state.isOpenVoice.value = true;
} }
} }
// static const int chunkSize = 320; // 32010ms G.711
Future<void> _onFrame(List<int> frame) async { static const int intervalMs = 40; // 40ms发送一次4chunk
// void _sendAudioChunk(Timer timer) async {
if (_bufferedAudioFrames.length > state.frameLength * 3) { if (_bufferedAudioFrames.length < chunkSize) {
_bufferedAudioFrames.clear(); // //
return; return;
} }
// // chunkSize
List<int> amplifiedFrame = _applyGain(frame, 1.6); final chunk = _bufferedAudioFrames.sublist(0, chunkSize);
// G711数据 //
List<int> encodedData = G711Tool.encode(amplifiedFrame, 0); // 0A-law _bufferedAudioFrames.removeRange(0, chunkSize);
_bufferedAudioFrames.addAll(encodedData);
// 使 //
final int ms = DateTime.now().millisecondsSinceEpoch % 1000000; // 使 final int ms = DateTime.now().millisecondsSinceEpoch % 1000000;
int getFrameLength = state.frameLength;
if (Platform.isIOS) { print('Send chunk ${timer.tick}: ${chunk.take(10).toList()}...');
getFrameLength = state.frameLength * 2;
}
//
if (_bufferedAudioFrames.length >= state.frameLength) {
try {
await StartChartManage().sendTalkDataMessage( await StartChartManage().sendTalkDataMessage(
talkData: TalkData( talkData: TalkData(
content: _bufferedAudioFrames, content: chunk,
contentType: TalkData_ContentTypeE.G711, contentType: TalkData_ContentTypeE.G711,
durationMs: ms, durationMs: ms,
), ),
); );
} finally {
_bufferedAudioFrames.clear(); //
} }
} else {
//
Future<void> _onFrame(List<int> frame) async {
final applyGain = _applyGain(frame, 1.6);
// G711数据
List<int> encodedData = G711Tool.encode(applyGain, 0); // 0A-law
_bufferedAudioFrames.addAll(encodedData); _bufferedAudioFrames.addAll(encodedData);
//
if (_startProcessingAudioTimer == null &&
_bufferedAudioFrames.length > chunkSize) {
_startProcessingAudioTimer =
Timer.periodic(Duration(milliseconds: intervalMs), _sendAudioChunk);
} }
} }
@ -649,27 +655,11 @@ class ImageTransmissionLogic extends BaseGetXController {
AppLog.log(error.message!); AppLog.log(error.message!);
} }
//
List<int> _applyGain(List<int> pcmData, double gainFactor) { List<int> _applyGain(List<int> pcmData, double gainFactor) {
List<int> result = List<int>.filled(pcmData.length, 0); return pcmData.map((sample) {
//
for (int i = 0; i < pcmData.length; i++) { int amplified = (sample * gainFactor).round();
// PCM数据通常是有符号的16位整数 return amplified.clamp(-32768, 32767);
int sample = pcmData[i]; }).toList();
//
double amplified = sample * gainFactor;
//
if (amplified > 32767) {
amplified = 32767;
} else if (amplified < -32768) {
amplified = -32768;
}
result[i] = amplified.toInt();
}
return result;
} }
} }

View File

@ -9,7 +9,6 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_pcm_sound/flutter_pcm_sound.dart'; import 'package:flutter_pcm_sound/flutter_pcm_sound.dart';
import 'package:flutter_voice_processor/flutter_voice_processor.dart'; import 'package:flutter_voice_processor/flutter_voice_processor.dart';
import 'package:gallery_saver/gallery_saver.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -18,28 +17,20 @@ import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/login/login/entity/LoginEntity.dart'; import 'package:star_lock/login/login/entity/LoginEntity.dart';
import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart';
import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_state.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_state.dart';
import 'package:star_lock/main/lockDetail/lockDetail/lockNetToken_entity.dart';
import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart';
import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart'; import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart';
import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/network/api_repository.dart';
import 'package:star_lock/talk/call/callTalk.dart';
import 'package:star_lock/talk/call/g711.dart';
import 'package:star_lock/talk/starChart/constant/talk_status.dart'; import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.dart'; import 'package:star_lock/talk/starChart/entity/scp_message.dart';
import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart'; import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart'; import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_data_h264_frame.pb.dart'; import 'package:star_lock/talk/starChart/proto/talk_data_h264_frame.pb.dart';
import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart'; import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart';
import 'package:star_lock/talk/starChart/star_chart_manage.dart'; import 'package:star_lock/talk/starChart/star_chart_manage.dart';
import 'package:star_lock/talk/starChart/views/native/talk_view_native_decode_state.dart'; import 'package:star_lock/talk/starChart/views/native/talk_view_native_decode_state.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart';
import 'package:star_lock/tools/G711Tool.dart'; import 'package:star_lock/tools/G711Tool.dart';
import 'package:star_lock/tools/bugly/bugly_tool.dart';
import 'package:star_lock/tools/callkit_handler.dart'; import 'package:star_lock/tools/callkit_handler.dart';
import 'package:star_lock/tools/commonDataManage.dart'; import 'package:star_lock/tools/commonDataManage.dart';
import 'package:star_lock/tools/storage.dart'; import 'package:star_lock/tools/storage.dart';
import 'package:video_decode_plugin/nalu_utils.dart';
import 'package:video_decode_plugin/video_decode_plugin.dart'; import 'package:video_decode_plugin/video_decode_plugin.dart';
import '../../../../tools/baseGetXController.dart'; import '../../../../tools/baseGetXController.dart';
@ -51,7 +42,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
int bufferSize = 25; // int bufferSize = 25; //
int audioBufferSize = 2; // 2 int audioBufferSize = 20; // 2
// frameSeq较小时阈值也小 // frameSeq较小时阈值也小
int _getFrameSeqRolloverThreshold(int lastSeq) { int _getFrameSeqRolloverThreshold(int lastSeq) {
@ -107,12 +98,16 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
state.isLoading.value = true; state.isLoading.value = true;
// //
final config = VideoDecoderConfig( final config = VideoDecoderConfig(
width: 864, width: StartChartManage().videoWidth,
// //
height: 480, height: StartChartManage().videoHeight,
codecType: 'h264', codecType: 'h264',
); );
// textureId // textureId
AppLog.log(
'StartChartManage().videoWidth:${StartChartManage().videoWidth}');
AppLog.log(
'StartChartManage().videoHeight:${StartChartManage().videoHeight}');
final textureId = await VideoDecodePlugin.initDecoder(config); final textureId = await VideoDecodePlugin.initDecoder(config);
if (textureId != null) { if (textureId != null) {
Future.microtask(() => state.textureId.value = textureId); Future.microtask(() => state.textureId.value = textureId);
@ -144,11 +139,11 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
FlutterPcmSound.setLogLevel(LogLevel.none); FlutterPcmSound.setLogLevel(LogLevel.none);
FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1); FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1);
// feed // feed
if (Platform.isAndroid) { // if (Platform.isAndroid) {
FlutterPcmSound.setFeedThreshold(1024); // Android // FlutterPcmSound.setFeedThreshold(1024); // Android
} else { // } else {
FlutterPcmSound.setFeedThreshold(2000); // Android // FlutterPcmSound.setFeedThreshold(4096); // Android
} // }
} }
/// ///
@ -498,16 +493,21 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
/// ///
void _playAudioData(TalkData talkData) async { void _playAudioData(TalkData talkData) async {
if (state.isOpenVoice.value && state.isLoading.isFalse) { if (state.isOpenVoice.value &&
final list = state.isLoading.isFalse &&
G711().decodeAndDenoise(talkData.content, true, 8000, 300, 150); state.isRecordingAudio.value == false) {
// // PCM PcmArrayInt16 List<int> encodedData = G711Tool.decode(talkData.content, 0); // 0A-law
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(list); // PCM PcmArrayInt16
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(encodedData);
FlutterPcmSound.feed(fromList); FlutterPcmSound.feed(fromList);
if (!state.isPlaying.value) { if (!state.isPlaying.value) {
AppLog.log('play');
FlutterPcmSound.play(); FlutterPcmSound.play();
state.isPlaying.value = true; state.isPlaying.value = true;
} }
} else if (state.isOpenVoice.isFalse) {
FlutterPcmSound.pause();
state.isPlaying.value = false;
} }
} }
@ -573,8 +573,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
void onInit() { void onInit() {
super.onInit(); super.onInit();
//
_startListenTalkData();
// //
_startListenTalkStatus(); _startListenTalkStatus();
// //
@ -596,6 +594,9 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
// H264帧缓冲区 // H264帧缓冲区
state.h264FrameBuffer.clear(); state.h264FrameBuffer.clear();
state.isProcessingFrame = false; state.isProcessingFrame = false;
//
_startListenTalkData();
} }
@override @override
@ -639,7 +640,9 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
// I帧集合 // I帧集合
_decodedIFrames.clear(); _decodedIFrames.clear();
_startProcessingAudioTimer?.cancel();
_startProcessingAudioTimer = null;
_bufferedAudioFrames.clear();
super.onClose(); super.onClose();
} }
@ -652,33 +655,12 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
/// ///
void updateTalkExpect() { void updateTalkExpect() {
// VideoTypeE的映射
final Map<String, VideoTypeE> qualityToVideoType = {
'标清': VideoTypeE.H264,
'高清': VideoTypeE.H264_720P,
//
};
TalkExpectReq talkExpectReq = TalkExpectReq();
state.isOpenVoice.value = !state.isOpenVoice.value; state.isOpenVoice.value = !state.isOpenVoice.value;
// videoType if (state.isOpenVoice.isTrue) {
VideoTypeE currentVideoType = FlutterPcmSound.play();
qualityToVideoType[state.currentQuality.value] ?? VideoTypeE.H264;
if (!state.isOpenVoice.value) {
talkExpectReq = TalkExpectReq(
videoType: [currentVideoType],
audioType: [],
);
showToast('已静音'.tr);
} else { } else {
talkExpectReq = TalkExpectReq( FlutterPcmSound.pause();
videoType: [currentVideoType],
audioType: [AudioTypeE.G711],
);
} }
///
StartChartManage().changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
talkExpect: talkExpectReq);
} }
/// ///
@ -762,6 +744,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
state.voiceProcessor = VoiceProcessor.instance; state.voiceProcessor = VoiceProcessor.instance;
} }
Timer? _startProcessingAudioTimer;
// //
Future<void> startProcessingAudio() async { Future<void> startProcessingAudio() async {
try { try {
@ -781,7 +765,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
} on PlatformException catch (ex) { } on PlatformException catch (ex) {
// state.errorMessage.value = 'Failed to start recorder: $ex'; // state.errorMessage.value = 'Failed to start recorder: $ex';
} }
state.isOpenVoice.value = false;
} }
/// ///
@ -801,47 +784,74 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
} on PlatformException catch (ex) { } on PlatformException catch (ex) {
// state.errorMessage.value = 'Failed to stop recorder: $ex'; // state.errorMessage.value = 'Failed to stop recorder: $ex';
} finally { } finally {
//
if (_startProcessingAudioTimer != null) {
// 53200
for (int i = 0; i < 5; i++) {
_bufferedAudioFrames.addAll(List.filled(chunkSize, 0));
}
Future.delayed(const Duration(milliseconds: 300), () {
_startProcessingAudioTimer?.cancel();
_startProcessingAudioTimer = null;
_bufferedAudioFrames.clear();
});
} else {
_bufferedAudioFrames.clear();
}
final bool? isRecording = await state.voiceProcessor?.isRecording(); final bool? isRecording = await state.voiceProcessor?.isRecording();
state.isRecordingAudio.value = isRecording!; state.isRecordingAudio.value = isRecording!;
state.isOpenVoice.value = true;
} }
} }
// //
Future<void> _onFrame(List<int> frame) async { List<int> _applyGain(List<int> pcmData, double gainFactor) {
// return pcmData.map((sample) {
if (_bufferedAudioFrames.length > state.frameLength * 3) { //
_bufferedAudioFrames.clear(); // int amplified = (sample * gainFactor).round();
return amplified.clamp(-32768, 32767);
}).toList();
}
static const int chunkSize = 320; // 32010ms G.711
static const int intervalMs = 35; // 40ms发送一次4chunk
void _sendAudioChunk(Timer timer) async {
if (_bufferedAudioFrames.length < chunkSize) {
//
return; return;
} }
// // chunkSize
List<int> amplifiedFrame = _applyGain(frame, 1.6); final chunk = _bufferedAudioFrames.sublist(0, chunkSize);
// G711数据 //
List<int> encodedData = G711Tool.encode(amplifiedFrame, 0); // 0A-law _bufferedAudioFrames.removeRange(0, chunkSize);
_bufferedAudioFrames.addAll(encodedData);
// 使 //
final int ms = DateTime.now().millisecondsSinceEpoch % 1000000; // 使 final int ms = DateTime.now().millisecondsSinceEpoch % 1000000;
int getFrameLength = state.frameLength;
if (Platform.isIOS) { print('Send chunk ${timer.tick}: ${chunk.take(10).toList()}...');
getFrameLength = state.frameLength * 2;
}
//
if (_bufferedAudioFrames.length >= state.frameLength) {
try {
await StartChartManage().sendTalkDataMessage( await StartChartManage().sendTalkDataMessage(
talkData: TalkData( talkData: TalkData(
content: _bufferedAudioFrames, content: chunk,
contentType: TalkData_ContentTypeE.G711, contentType: TalkData_ContentTypeE.G711,
durationMs: ms, durationMs: ms,
), ),
); );
} finally {
_bufferedAudioFrames.clear(); //
} }
} else {
//
Future<void> _onFrame(List<int> frame) async {
final applyGain = _applyGain(frame, 1.6);
// G711数据
List<int> encodedData = G711Tool.encode(applyGain, 0); // 0A-law
_bufferedAudioFrames.addAll(encodedData); _bufferedAudioFrames.addAll(encodedData);
//
if (_startProcessingAudioTimer == null &&
_bufferedAudioFrames.length > chunkSize) {
_startProcessingAudioTimer =
Timer.periodic(Duration(milliseconds: intervalMs), _sendAudioChunk);
} }
} }
@ -850,476 +860,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
AppLog.log(error.message!); AppLog.log(error.message!);
} }
//
List<int> _applyGain(List<int> pcmData, double gainFactor) {
List<int> result = List<int>.filled(pcmData.length, 0);
for (int i = 0; i < pcmData.length; i++) {
// PCM数据通常是有符号的16位整数
int sample = pcmData[i];
//
double amplified = sample * gainFactor;
//
if (amplified > 32767) {
amplified = 32767;
} else if (amplified < -32768) {
amplified = -32768;
}
result[i] = amplified.toInt();
}
return result;
}
/// h264文件frameType
Future<void> _appendH264FrameToFile(
List<int> frameData, TalkDataH264Frame_FrameTypeE frameType) async {
try {
if (state.h264File == null) {
await _initH264File();
}
// NALU分割函数NALU的完整字节数组
List<List<int>> splitNalus(List<int> data) {
List<List<int>> nalus = [];
int i = 0;
while (i < data.length - 3) {
int start = -1;
int next = -1;
if (data[i] == 0x00 && data[i + 1] == 0x00) {
if (data[i + 2] == 0x01) {
start = i;
i += 3;
} else if (i + 3 < data.length &&
data[i + 2] == 0x00 &&
data[i + 3] == 0x01) {
start = i;
i += 4;
} else {
i++;
continue;
}
next = i;
while (next < data.length - 3) {
if (data[next] == 0x00 &&
data[next + 1] == 0x00 &&
((data[next + 2] == 0x01) ||
(data[next + 2] == 0x00 && data[next + 3] == 0x01))) {
break;
}
next++;
}
nalus.add(data.sublist(start, next));
i = next;
} else {
i++;
}
}
int nalusTotalLen =
nalus.isNotEmpty ? nalus.fold(0, (p, n) => p + n.length) : 0;
if (nalus.isEmpty && data.isNotEmpty) {
nalus.add(data);
} else if (nalus.isNotEmpty && nalusTotalLen < data.length) {
nalus.add(data.sublist(nalusTotalLen));
}
return nalus;
}
// I帧前只缓存SPS/PPS/IDR
if (!_hasWrittenFirstIFrame) {
final nalus = splitNalus(frameData);
List<List<int>> spsList = [];
List<List<int>> ppsList = [];
List<List<int>> idrList = [];
for (final nalu in nalus) {
int offset = 0;
if (nalu.length >= 4 && nalu[0] == 0x00 && nalu[1] == 0x00) {
if (nalu[2] == 0x01)
offset = 3;
else if (nalu[2] == 0x00 && nalu[3] == 0x01) offset = 4;
}
if (nalu.length > offset) {
int naluType = nalu[offset] & 0x1F;
if (naluType == 7) {
spsList.add(nalu);
// AppLog.log('SPS内容: ' +
// nalu
// .map((b) => b.toRadixString(16).padLeft(2, '0'))
// .join(' '));
} else if (naluType == 8) {
ppsList.add(nalu);
// AppLog.log('PPS内容: ' +
// nalu
// .map((b) => b.toRadixString(16).padLeft(2, '0'))
// .join(' '));
} else if (naluType == 5) {
idrList.add(nalu);
}
//
}
}
// I帧写入前缓存
if (spsList.isNotEmpty && ppsList.isNotEmpty && idrList.isNotEmpty) {
for (final sps in spsList) {
await _writeSingleFrameToFile(_ensureStartCode(sps));
// AppLog.log('写入顺序: SPS');
}
for (final pps in ppsList) {
await _writeSingleFrameToFile(_ensureStartCode(pps));
// AppLog.log('写入顺序: PPS');
}
for (final idr in idrList) {
await _writeSingleFrameToFile(_ensureStartCode(idr));
// AppLog.log('写入顺序: IDR');
}
_hasWrittenFirstIFrame = true;
} else {
// SPS/PPS/IDR则继续缓存I帧
if (spsList.isNotEmpty) _preIFrameCache.addAll(spsList);
if (ppsList.isNotEmpty) _preIFrameCache.addAll(ppsList);
if (idrList.isNotEmpty) _preIFrameCache.addAll(idrList);
}
} else {
// IDR和P帧
final nalus = splitNalus(frameData);
for (final nalu in nalus) {
int offset = 0;
if (nalu.length >= 4 && nalu[0] == 0x00 && nalu[1] == 0x00) {
if (nalu[2] == 0x01)
offset = 3;
else if (nalu[2] == 0x00 && nalu[3] == 0x01) offset = 4;
}
if (nalu.length > offset) {
int naluType = nalu[offset] & 0x1F;
if (naluType == 5) {
await _writeSingleFrameToFile(_ensureStartCode(nalu));
// AppLog.log('写入顺序: IDR');
} else if (naluType == 1) {
await _writeSingleFrameToFile(_ensureStartCode(nalu));
// AppLog.log('写入顺序: P帧');
} else if (naluType == 7) {
// AppLog.log('遇到新SPS已忽略');
} else if (naluType == 8) {
// AppLog.log('遇到新PPS已忽略');
}
//
}
}
}
} catch (e) {
AppLog.log('写入H264帧到文件失败: $e');
}
}
// NALU起始码为0x00000001
List<int> _ensureStartCode(List<int> nalu) {
if (nalu.length >= 4 &&
nalu[0] == 0x00 &&
nalu[1] == 0x00 &&
nalu[2] == 0x00 &&
nalu[3] == 0x01) {
return nalu;
} else if (nalu.length >= 3 &&
nalu[0] == 0x00 &&
nalu[1] == 0x00 &&
nalu[2] == 0x01) {
return [0x00, 0x00, 0x00, 0x01] + nalu.sublist(3);
} else {
return [0x00, 0x00, 0x00, 0x01] + nalu;
}
}
/// NALU头判断
Future<void> _writeSingleFrameToFile(List<int> frameData) async {
bool hasNaluHeader = false;
if (frameData.length >= 4 &&
frameData[0] == 0x00 &&
frameData[1] == 0x00 &&
((frameData[2] == 0x01) ||
(frameData[2] == 0x00 && frameData[3] == 0x01))) {
hasNaluHeader = true;
}
if (hasNaluHeader) {
await state.h264File!.writeAsBytes(frameData, mode: FileMode.append);
} else {
final List<int> naluHeader = [0x00, 0x00, 0x01];
await state.h264File!
.writeAsBytes(naluHeader + frameData, mode: FileMode.append);
}
}
/// h264文件
Future<void> _initH264File() async {
try {
if (state.h264File != null) return;
// Download目录
Directory? downloadsDir;
if (Platform.isAndroid) {
// Android 10+ getExternalStorageDirectory()
downloadsDir = await getExternalStorageDirectory();
// ROMDownload
final downloadPath = '/storage/emulated/0/Download';
if (Directory(downloadPath).existsSync()) {
downloadsDir = Directory(downloadPath);
}
} else {
downloadsDir = await getApplicationDocumentsDirectory();
}
final filePath =
'${downloadsDir!.path}/video_${DateTime.now().millisecondsSinceEpoch}.h264';
state.h264FilePath = filePath;
state.h264File = File(filePath);
if (!await state.h264File!.exists()) {
await state.h264File!.create(recursive: true);
}
AppLog.log('H264文件初始化: $filePath');
} catch (e) {
AppLog.log('H264文件初始化失败: $e');
}
}
/// h264文件
Future<void> _closeH264File() async {
try {
if (state.h264File != null) {
AppLog.log('H264文件已关闭: ${state.h264FilePath ?? ''}');
}
state.h264File = null;
state.h264FilePath = null;
_preIFrameCache.clear();
_hasWrittenFirstIFrame = false;
} catch (e) {
AppLog.log('关闭H264文件时出错: $e');
}
}
/// I帧数据中分割NALU并将SPS/PPS优先放入缓冲区
void _extractAndBufferSpsPpsForBuffer(
List<int> frameData, int durationMs, int frameSeq, int frameSeqI) {
List<List<int>> splitNalus(List<int> data) {
List<List<int>> nalus = [];
int i = 0;
while (i < data.length - 3) {
int start = -1;
int next = -1;
if (data[i] == 0x00 && data[i + 1] == 0x00) {
if (data[i + 2] == 0x01) {
start = i;
i += 3;
} else if (i + 3 < data.length &&
data[i + 2] == 0x00 &&
data[i + 3] == 0x01) {
start = i;
i += 4;
} else {
i++;
continue;
}
next = i;
while (next < data.length - 3) {
if (data[next] == 0x00 &&
data[next + 1] == 0x00 &&
((data[next + 2] == 0x01) ||
(data[next + 2] == 0x00 && data[next + 3] == 0x01))) {
break;
}
next++;
}
nalus.add(data.sublist(start, next));
i = next;
} else {
i++;
}
}
int nalusTotalLen =
nalus.isNotEmpty ? nalus.fold(0, (p, n) => p + n.length) : 0;
if (nalus.isEmpty && data.isNotEmpty) {
nalus.add(data);
} else if (nalus.isNotEmpty && nalusTotalLen < data.length) {
nalus.add(data.sublist(nalusTotalLen));
}
return nalus;
}
final nalus = splitNalus(frameData);
for (final nalu in nalus) {
int offset = 0;
if (nalu.length >= 4 && nalu[0] == 0x00 && nalu[1] == 0x00) {
if (nalu[2] == 0x01)
offset = 3;
else if (nalu[2] == 0x00 && nalu[3] == 0x01) offset = 4;
}
if (nalu.length > offset) {
int naluType = nalu[offset] & 0x1F;
if (naluType == 7) {
// SPS
hasSps = true;
//
if (spsCache == null || !_listEquals(spsCache!, nalu)) {
spsCache = List<int>.from(nalu);
}
} else if (naluType == 8) {
// PPS
hasPps = true;
if (ppsCache == null || !_listEquals(ppsCache!, nalu)) {
ppsCache = List<int>.from(nalu);
}
}
}
}
}
// List比较工具
bool _listEquals(List<int> a, List<int> b) {
if (a.length != b.length) return false;
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
// I帧处理方法
// void _handleIFrameWithSpsPpsAndIdr(
// List<int> frameData, int durationMs, int frameSeq, int frameSeqI) {
// // I帧前所有未处理帧SPS/PPS/I帧
// state.h264FrameBuffer.clear();
// _extractAndBufferSpsPpsForBuffer(
// frameData, durationMs, frameSeq, frameSeqI);
// // SPS/PPS就先写入I帧本体IDR
// if (spsCache == null || ppsCache == null) {
// // SPS/PPS缓存I帧
// return;
// }
// // SPS/PPS
// _addFrameToBuffer(spsCache!, TalkDataH264Frame_FrameTypeE.I, durationMs,
// frameSeq, frameSeqI);
// _addFrameToBuffer(ppsCache!, TalkDataH264Frame_FrameTypeE.I, durationMs,
// frameSeq, frameSeqI);
// // I帧包IDRtype 5
// List<List<int>> nalus = [];
// int i = 0;
// List<int> data = frameData;
// while (i < data.length - 3) {
// int start = -1;
// int next = -1;
// if (data[i] == 0x00 && data[i + 1] == 0x00) {
// if (data[i + 2] == 0x01) {
// start = i;
// i += 3;
// } else if (i + 3 < data.length &&
// data[i + 2] == 0x00 &&
// data[i + 3] == 0x01) {
// start = i;
// i += 4;
// } else {
// i++;
// continue;
// }
// next = i;
// while (next < data.length - 3) {
// if (data[next] == 0x00 &&
// data[next + 1] == 0x00 &&
// ((data[next + 2] == 0x01) ||
// (data[next + 2] == 0x00 && data[next + 3] == 0x01))) {
// break;
// }
// next++;
// }
// nalus.add(data.sublist(start, next));
// i = next;
// } else {
// i++;
// }
// }
// int nalusTotalLen =
// nalus.isNotEmpty ? nalus.fold(0, (p, n) => p + n.length) : 0;
// if (nalus.isEmpty && data.isNotEmpty) {
// nalus.add(data);
// } else if (nalus.isNotEmpty && nalusTotalLen < data.length) {
// nalus.add(data.sublist(nalusTotalLen));
// }
// for (final nalu in nalus) {
// int offset = 0;
// if (nalu.length >= 4 && nalu[0] == 0x00 && nalu[1] == 0x00) {
// if (nalu[2] == 0x01)
// offset = 3;
// else if (nalu[2] == 0x00 && nalu[3] == 0x01) offset = 4;
// }
// if (nalu.length > offset) {
// int naluType = nalu[offset] & 0x1F;
// if (naluType == 5) {
// _addFrameToBuffer(nalu, TalkDataH264Frame_FrameTypeE.I, durationMs,
// frameSeq, frameSeqI);
// }
// }
// }
// }
// P帧处理方法
// void _handlePFrame(
// List<int> frameData, int durationMs, int frameSeq, int frameSeqI) {
// // P帧type 1
// List<List<int>> nalus = [];
// int i = 0;
// List<int> data = frameData;
// while (i < data.length - 3) {
// int start = -1;
// int next = -1;
// if (data[i] == 0x00 && data[i + 1] == 0x00) {
// if (data[i + 2] == 0x01) {
// start = i;
// i += 3;
// } else if (i + 3 < data.length &&
// data[i + 2] == 0x00 &&
// data[i + 3] == 0x01) {
// start = i;
// i += 4;
// } else {
// i++;
// continue;
// }
// next = i;
// while (next < data.length - 3) {
// if (data[next] == 0x00 &&
// data[next + 1] == 0x00 &&
// ((data[next + 2] == 0x01) ||
// (data[next + 2] == 0x00 && data[next + 3] == 0x01))) {
// break;
// }
// next++;
// }
// nalus.add(data.sublist(start, next));
// i = next;
// } else {
// i++;
// }
// }
// int nalusTotalLen =
// nalus.isNotEmpty ? nalus.fold(0, (p, n) => p + n.length) : 0;
// if (nalus.isEmpty && data.isNotEmpty) {
// nalus.add(data);
// } else if (nalus.isNotEmpty && nalusTotalLen < data.length) {
// nalus.add(data.sublist(nalusTotalLen));
// }
// for (final nalu in nalus) {
// int offset = 0;
// if (nalu.length >= 4 && nalu[0] == 0x00 && nalu[1] == 0x00) {
// if (nalu[2] == 0x01)
// offset = 3;
// else if (nalu[2] == 0x00 && nalu[3] == 0x01) offset = 4;
// }
// if (nalu.length > offset) {
// int naluType = nalu[offset] & 0x1F;
// if (naluType == 1) {
// _addFrameToBuffer(nalu, TalkDataH264Frame_FrameTypeE.P, durationMs,
// frameSeq, frameSeqI);
// }
// }
// }
// }
// //
void onQualityChanged(String quality) async { void onQualityChanged(String quality) async {
state.currentQuality.value = quality; state.currentQuality.value = quality;
@ -1432,6 +972,10 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
// //
switch (contentType) { switch (contentType) {
case TalkData_ContentTypeE.G711: case TalkData_ContentTypeE.G711:
//
if (!state.isOpenVoice.value || state.isRecordingAudio.value) {
return;
}
if (state.audioBuffer.length >= audioBufferSize) { if (state.audioBuffer.length >= audioBufferSize) {
state.audioBuffer.removeAt(0); // state.audioBuffer.removeAt(0); //
} }

View File

@ -109,22 +109,10 @@ class TalkViewLogic extends BaseGetXController {
// //
switch (contentType) { switch (contentType) {
case TalkData_ContentTypeE.G711: case TalkData_ContentTypeE.G711:
// // //
if (_isFirstAudioFrame) { if (!state.isOpenVoice.value || state.isRecordingAudio.value) {
_startAudioTime = currentTime; print(
_isFirstAudioFrame = false; '录音时丢弃数据:${state.isOpenVoice.value}-${state.isRecordingAudio.value}');
}
//
final expectedTime = _startAudioTime + talkData.durationMs;
final audioDelay = currentTime - expectedTime;
//
if (audioDelay > 500) {
state.audioBuffer.clear();
if (state.isOpenVoice.value) {
_playAudioFrames();
}
return; return;
} }
if (state.audioBuffer.length >= audioBufferSize) { if (state.audioBuffer.length >= audioBufferSize) {
@ -388,9 +376,11 @@ class TalkViewLogic extends BaseGetXController {
if (state.videoBuffer.isNotEmpty) { if (state.videoBuffer.isNotEmpty) {
final TalkData oldestFrame = state.videoBuffer.removeAt(0); final TalkData oldestFrame = state.videoBuffer.removeAt(0);
if (oldestFrame.content.isNotEmpty) { if (oldestFrame.content.isNotEmpty) {
state.listData.value = Uint8List.fromList(oldestFrame.content); // state.listData.value =
Uint8List.fromList(oldestFrame.content); //
final int decodeStart = DateTime.now().millisecondsSinceEpoch; final int decodeStart = DateTime.now().millisecondsSinceEpoch;
decodeImageFromList(Uint8List.fromList(oldestFrame.content)).then((ui.Image img) { decodeImageFromList(Uint8List.fromList(oldestFrame.content))
.then((ui.Image img) {
final int decodeEnd = DateTime.now().millisecondsSinceEpoch; final int decodeEnd = DateTime.now().millisecondsSinceEpoch;
state.currentImage.value = img; state.currentImage.value = img;
_renderedFrameCount++; _renderedFrameCount++;
@ -558,6 +548,8 @@ class TalkViewLogic extends BaseGetXController {
state.voiceProcessor = VoiceProcessor.instance; state.voiceProcessor = VoiceProcessor.instance;
} }
Timer? _startProcessingAudioTimer;
// //
Future<void> startProcessingAudio() async { Future<void> startProcessingAudio() async {
try { try {
@ -577,7 +569,6 @@ class TalkViewLogic extends BaseGetXController {
} on PlatformException catch (ex) { } on PlatformException catch (ex) {
// state.errorMessage.value = 'Failed to start recorder: $ex'; // state.errorMessage.value = 'Failed to start recorder: $ex';
} }
state.isOpenVoice.value = false;
} }
/// ///
@ -597,47 +588,65 @@ class TalkViewLogic extends BaseGetXController {
} on PlatformException catch (ex) { } on PlatformException catch (ex) {
// state.errorMessage.value = 'Failed to stop recorder: $ex'; // state.errorMessage.value = 'Failed to stop recorder: $ex';
} finally { } finally {
//
if (_startProcessingAudioTimer != null) {
// 53200
for (int i = 0; i < 5; i++) {
_bufferedAudioFrames.addAll(List.filled(chunkSize, 0));
}
Future.delayed(const Duration(milliseconds: 300), () {
_startProcessingAudioTimer?.cancel();
_startProcessingAudioTimer = null;
_bufferedAudioFrames.clear();
});
} else {
_bufferedAudioFrames.clear();
}
final bool? isRecording = await state.voiceProcessor?.isRecording(); final bool? isRecording = await state.voiceProcessor?.isRecording();
state.isRecordingAudio.value = isRecording!; state.isRecordingAudio.value = isRecording!;
state.isOpenVoice.value = true;
} }
} }
// static const int chunkSize = 320; // 32010ms G.711
Future<void> _onFrame(List<int> frame) async { static const int intervalMs = 40; // 40ms发送一次4chunk
// void _sendAudioChunk(Timer timer) async {
if (_bufferedAudioFrames.length > state.frameLength * 3) { if (_bufferedAudioFrames.length < chunkSize) {
_bufferedAudioFrames.clear(); // //
return; return;
} }
// // chunkSize
List<int> amplifiedFrame = _applyGain(frame, 1.6); final chunk = _bufferedAudioFrames.sublist(0, chunkSize);
// G711数据 //
List<int> encodedData = G711Tool.encode(amplifiedFrame, 0); // 0A-law _bufferedAudioFrames.removeRange(0, chunkSize);
_bufferedAudioFrames.addAll(encodedData);
// 使 //
final int ms = DateTime.now().millisecondsSinceEpoch % 1000000; // 使 final int ms = DateTime.now().millisecondsSinceEpoch % 1000000;
int getFrameLength = state.frameLength;
if (Platform.isIOS) { print('Send chunk ${timer.tick}: ${chunk.take(10).toList()}...');
getFrameLength = state.frameLength * 2;
}
//
if (_bufferedAudioFrames.length >= state.frameLength) {
try {
await StartChartManage().sendTalkDataMessage( await StartChartManage().sendTalkDataMessage(
talkData: TalkData( talkData: TalkData(
content: _bufferedAudioFrames, content: chunk,
contentType: TalkData_ContentTypeE.G711, contentType: TalkData_ContentTypeE.G711,
durationMs: ms, durationMs: ms,
), ),
); );
} finally {
_bufferedAudioFrames.clear(); //
} }
} else {
//
Future<void> _onFrame(List<int> frame) async {
final applyGain = _applyGain(frame, 1.6);
// G711数据
List<int> encodedData = G711Tool.encode(applyGain, 0); // 0A-law
_bufferedAudioFrames.addAll(encodedData); _bufferedAudioFrames.addAll(encodedData);
//
if (_startProcessingAudioTimer == null &&
_bufferedAudioFrames.length > chunkSize) {
_startProcessingAudioTimer =
Timer.periodic(Duration(milliseconds: intervalMs), _sendAudioChunk);
} }
} }
@ -648,25 +657,10 @@ class TalkViewLogic extends BaseGetXController {
// //
List<int> _applyGain(List<int> pcmData, double gainFactor) { List<int> _applyGain(List<int> pcmData, double gainFactor) {
List<int> result = List<int>.filled(pcmData.length, 0); return pcmData.map((sample) {
//
for (int i = 0; i < pcmData.length; i++) { int amplified = (sample * gainFactor).round();
// PCM数据通常是有符号的16位整数 return amplified.clamp(-32768, 32767);
int sample = pcmData[i]; }).toList();
//
double amplified = sample * gainFactor;
//
if (amplified > 32767) {
amplified = 32767;
} else if (amplified < -32768) {
amplified = -32768;
}
result[i] = amplified.toInt();
}
return result;
} }
} }

View File

@ -21,6 +21,7 @@ class CommonItem extends StatelessWidget {
this.isTipsImg, this.isTipsImg,
this.action, this.action,
this.leftTitleMaxWidth, // this.leftTitleMaxWidth, //
this.leftTitleStyle, //
this.tipsImgAction}) this.tipsImgAction})
: super(key: key); : super(key: key);
String? leftTitel; String? leftTitel;
@ -35,6 +36,7 @@ class CommonItem extends StatelessWidget {
bool? setHeight; bool? setHeight;
bool? isTipsImg; bool? isTipsImg;
bool? isPadding; bool? isPadding;
TextStyle? leftTitleStyle; //
final double? leftTitleMaxWidth; // final double? leftTitleMaxWidth; //
@override @override
@ -65,7 +67,7 @@ class CommonItem extends StatelessWidget {
), ),
child: Text( child: Text(
leftTitel!, leftTitel!,
style: TextStyle(fontSize: 22.sp), style: leftTitleStyle ?? TextStyle(fontSize: 22.sp),
overflow: TextOverflow.ellipsis, // overflow: TextOverflow.ellipsis, //
maxLines: 3, // 2 maxLines: 3, // 2
), ),

View File

@ -131,6 +131,13 @@ class ReadMessageRefreshUI {
ReadMessageRefreshUI(); ReadMessageRefreshUI();
} }
///
class ReadTalkMessageRefreshUI {
ReadTalkMessageRefreshUI(this.lockName);
String lockName;
}
/// ///
class ElectronicKeyListRefreshUI { class ElectronicKeyListRefreshUI {
ElectronicKeyListRefreshUI(); ElectronicKeyListRefreshUI();

View File

@ -206,130 +206,130 @@ extension ExtensionLanguageType on LanguageType {
var str = ''; var str = '';
switch (this) { switch (this) {
case LanguageType.english: case LanguageType.english:
str = '英文'.tr; str = '英文'.tr + 'English';
break; break;
case LanguageType.chinese: case LanguageType.chinese:
str = '简体中文'.tr; str = '简体中文'.tr + 'Simplified Chinese';
break; break;
case LanguageType.traditionalChineseTW: case LanguageType.traditionalChineseTW:
str = '繁体中文(中国台湾)'.tr; str = '繁体中文(中国台湾)'.tr + 'Traditional Chinese TW';
break; break;
case LanguageType.traditionalChineseHK: case LanguageType.traditionalChineseHK:
str = '繁体中文(中国香港)'.tr; str = '繁体中文(中国香港)'.tr + 'Traditional Chinese HK';
break; break;
case LanguageType.french: case LanguageType.french:
str = '法语'.tr; str = '法语'.tr + 'French';
break; break;
case LanguageType.russian: case LanguageType.russian:
str = '俄语'.tr; str = '俄语'.tr + 'Russian';
break; break;
case LanguageType.german: case LanguageType.german:
str = '德语'.tr; str = '德语'.tr + 'German';
break; break;
case LanguageType.japanese: case LanguageType.japanese:
str = '日语'.tr; str = '日语'.tr + 'Japanese';
break; break;
case LanguageType.korean: case LanguageType.korean:
str = '韩语'.tr; str = '韩语'.tr + 'Korean';
break; break;
case LanguageType.italian: case LanguageType.italian:
str = '意大利语'.tr; str = '意大利语'.tr + 'Italian';
break; break;
case LanguageType.portuguese: case LanguageType.portuguese:
str = '葡萄牙语'.tr; str = '葡萄牙语'.tr + 'Portuguese';
break; break;
case LanguageType.spanish: case LanguageType.spanish:
str = '西班牙语'.tr; str = '西班牙语'.tr + 'Spanish';
break; break;
case LanguageType.arabic: case LanguageType.arabic:
str = '阿拉伯语'.tr; str = '阿拉伯语'.tr + 'Arabic';
break; break;
case LanguageType.vietnamese: case LanguageType.vietnamese:
str = '越南语'.tr; str = '越南语'.tr + 'Vietnamese';
break; break;
case LanguageType.malay: case LanguageType.malay:
str = '马来语'.tr; str = '马来语'.tr + 'Malay';
break; break;
case LanguageType.dutch: case LanguageType.dutch:
str = '荷兰语'.tr; str = '荷兰语'.tr + 'Dutch';
break; break;
case LanguageType.romanian: case LanguageType.romanian:
str = '罗马尼亚语'.tr; str = '罗马尼亚语'.tr + 'Romanian';
break; break;
case LanguageType.lithuanian: case LanguageType.lithuanian:
str = '立陶宛语'.tr; str = '立陶宛语'.tr + 'Lithuanian';
break; break;
case LanguageType.swedish: case LanguageType.swedish:
str = '瑞典语'.tr; str = '瑞典语'.tr + 'Swedish';
break; break;
case LanguageType.estonian: case LanguageType.estonian:
str = '爱沙尼亚语'.tr; str = '爱沙尼亚语'.tr + 'Estonian';
break; break;
case LanguageType.polish: case LanguageType.polish:
str = '波兰语'.tr; str = '波兰语'.tr + 'Polish';
break; break;
case LanguageType.slovak: case LanguageType.slovak:
str = '斯洛伐克语'.tr; str = '斯洛伐克语'.tr + 'Slovak';
break; break;
case LanguageType.czech: case LanguageType.czech:
str = '捷克语'.tr; str = '捷克语'.tr + 'Czech';
break; break;
case LanguageType.greek: case LanguageType.greek:
str = '希腊语'.tr; str = '希腊语'.tr + 'Greek';
break; break;
case LanguageType.hebrew: case LanguageType.hebrew:
str = '希伯来语'.tr; str = '希伯来语'.tr + 'Hebrew';
break; break;
case LanguageType.serbian: case LanguageType.serbian:
str = '塞尔维亚语'.tr; str = '塞尔维亚语'.tr + 'Serbian';
break; break;
case LanguageType.turkish: case LanguageType.turkish:
str = '土耳其语'.tr; str = '土耳其语'.tr + 'Turkish';
break; break;
case LanguageType.hungarian: case LanguageType.hungarian:
str = '匈牙利语'.tr; str = '匈牙利语'.tr + 'Hungarian';
break; break;
case LanguageType.bulgarian: case LanguageType.bulgarian:
str = '保加利亚语'.tr; str = '保加利亚语'.tr + 'Bulgarian';
break; break;
case LanguageType.kazakh: case LanguageType.kazakh:
str = '哈萨克斯坦语'.tr; str = '哈萨克斯坦语'.tr + 'Kazakh';
break; break;
case LanguageType.bengali: case LanguageType.bengali:
str = '孟加拉语'.tr; str = '孟加拉语'.tr + 'Bengali';
break; break;
case LanguageType.croatian: case LanguageType.croatian:
str = '克罗地亚语'.tr; str = '克罗地亚语'.tr + 'Croatian';
break; break;
case LanguageType.thai: case LanguageType.thai:
str = '泰语'.tr; str = '泰语'.tr + 'Thai';
break; break;
case LanguageType.indonesian: case LanguageType.indonesian:
str = '印度尼西亚语'.tr; str = '印度尼西亚语'.tr + 'Indonesian';
break; break;
case LanguageType.finnish: case LanguageType.finnish:
str = '芬兰语'.tr; str = '芬兰语'.tr + 'Finnish';
break; break;
case LanguageType.danish: case LanguageType.danish:
str = '丹麦语'.tr; str = '丹麦语'.tr + 'Danish';
break; break;
case LanguageType.ukrainian: case LanguageType.ukrainian:
str = '乌克兰语'.tr; str = '乌克兰语'.tr + 'Ukrainian';
break; break;
case LanguageType.hindi: case LanguageType.hindi:
str = '印地语'.tr; str = '印地语'.tr + 'Hindi';
break; break;
case LanguageType.urdu: case LanguageType.urdu:
str = '乌尔都语'.tr; str = '乌尔都语'.tr + 'Urdu';
break; break;
case LanguageType.armenian: case LanguageType.armenian:
str = '亚美尼亚语'.tr; str = '亚美尼亚语'.tr + 'Armenian';
break; break;
case LanguageType.georgian: case LanguageType.georgian:
str = '格鲁吉亚语'.tr; str = '格鲁吉亚语'.tr + 'Georgian';
break; break;
case LanguageType.brazilianPortuguese: case LanguageType.brazilianPortuguese:
str = '巴西葡萄牙语'.tr; str = '巴西葡萄牙语'.tr + 'Brazilian Portuguese';
break; break;
} }
return str; return str;