redesign explore
|
|
@ -0,0 +1,7 @@
|
|||
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="56" height="56" rx="28" fill="#195D80"/>
|
||||
<path d="M16 41.333H40" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19.4666 23.1738H17.3333C16.6 23.1738 16 23.7738 16 24.5072V36.0005C16 36.7338 16.6 37.3338 17.3333 37.3338H19.4666C20.2 37.3338 20.8 36.7338 20.8 36.0005V24.5072C20.8 23.7738 20.2 23.1738 19.4666 23.1738Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M29.0662 18.9199H26.9329C26.1996 18.9199 25.5996 19.5199 25.5996 20.2533V35.9999C25.5996 36.7333 26.1996 37.3333 26.9329 37.3333H29.0662C29.7996 37.3333 30.3996 36.7333 30.3996 35.9999V20.2533C30.3996 19.5199 29.7996 18.9199 29.0662 18.9199Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M38.6668 14.667H36.5335C35.8002 14.667 35.2002 15.267 35.2002 16.0003V36.0003C35.2002 36.7337 35.8002 37.3337 36.5335 37.3337H38.6668C39.4002 37.3337 40.0002 36.7337 40.0002 36.0003V16.0003C40.0002 15.267 39.4002 14.667 38.6668 14.667Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="23" height="23" viewBox="0 0 23 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.7132 1.92049C11.7684 1.80132 11.7996 1.67243 11.8051 1.54119C11.8105 1.40995 11.79 1.27893 11.7448 1.1556C11.6996 1.03228 11.6305 0.919077 11.5415 0.822457C11.4525 0.725837 11.3454 0.647694 11.2262 0.592493C9.60016 -0.161507 7.66016 -0.222507 5.56417 0.563493C3.89767 1.18799 2.51967 1.09099 1.44517 0.592493C1.32602 0.537239 1.19717 0.505993 1.06595 0.500539C0.934731 0.495085 0.803724 0.515529 0.680408 0.560705C0.557091 0.60588 0.443881 0.674903 0.34724 0.763831C0.250598 0.852759 0.172419 0.959851 0.117165 1.07899C0.0619114 1.19813 0.0306655 1.32699 0.0252114 1.45821C0.0197572 1.58943 0.0402016 1.72043 0.0853773 1.84375C0.130553 1.96707 0.199575 2.08028 0.288503 2.17692C0.377431 2.27356 0.484523 2.35174 0.603665 2.40699C2.23017 3.16099 4.17017 3.22199 6.26616 2.43599C7.93216 1.81149 9.31017 1.90849 10.3852 2.40699C10.5043 2.46226 10.6332 2.49351 10.7644 2.49897C10.8956 2.50443 11.0266 2.48398 11.1499 2.43881C11.2733 2.39363 11.3865 2.3246 11.4831 2.23567C11.5797 2.14674 11.6579 2.03964 11.7132 1.92049ZM18.4822 6.24549C18.6357 7.60599 18.8137 9.27349 18.9907 11.122H13.6832C13.8602 9.27349 14.0382 7.60599 14.1912 6.24549H18.4822ZM13.0112 3.43849C12.8332 4.88399 12.5012 7.70349 12.1762 11.122H10.7552C10.7322 10.217 10.7017 9.47899 10.6727 8.77549L10.6702 8.71849C10.6052 7.14649 9.06017 5.92649 7.46867 6.55249C4.63117 7.66899 2.65217 9.04999 1.49967 10.0025C0.467165 10.856 0.0246652 12.128 0.0246652 13.349C0.0246652 13.828 0.0181652 14.312 0.0121652 14.8C-0.00883484 16.517 -0.0303349 18.2865 0.203165 20.083C0.399165 21.591 1.64967 22.5495 3.03067 22.6085C4.56067 22.6735 7.23566 22.752 11.2537 22.752C15.2717 22.752 17.9477 22.6735 19.4772 22.6085C20.8582 22.5495 22.1087 21.591 22.3047 20.083C22.5257 18.381 22.5082 16.5605 22.4917 14.879V14.877C22.4877 14.4485 22.4837 14.029 22.4837 13.6225C22.4837 12.4285 21.6482 11.397 20.5017 11.169C20.2567 8.58868 19.977 6.01178 19.6627 3.43899C19.5477 2.50099 18.8367 1.69399 17.8292 1.58299C17.3335 1.52843 16.8353 1.50039 16.3367 1.49899C15.7772 1.49899 15.2632 1.53699 14.8447 1.58299C13.8372 1.69449 13.1267 2.50099 13.0112 3.43849ZM8.20117 8.41349C8.24858 8.39337 8.30044 8.38601 8.35158 8.39215C8.40272 8.39829 8.45136 8.41772 8.49267 8.44849C8.58467 8.51299 8.66467 8.63249 8.67167 8.80099L8.67416 8.85549C8.71316 9.79549 8.75417 10.7795 8.77567 12.138C8.77986 12.4004 8.88707 12.6507 9.07416 12.8348C9.26124 13.0189 9.5132 13.122 9.77567 13.122H20.0232C20.2522 13.122 20.4832 13.32 20.4832 13.6225C20.4832 14.074 20.4877 14.5245 20.4917 14.9725C20.5077 16.6385 20.5227 18.2755 20.3217 19.825C20.2652 20.2585 19.9167 20.588 19.3917 20.61C17.8932 20.674 15.2467 20.752 11.2537 20.752C10.6683 20.752 10.1113 20.7503 9.58267 20.747C9.58533 20.4437 9.58667 20.1058 9.58667 19.7335C9.58667 18.5275 9.57016 17.695 9.55066 17.131C9.52067 16.272 9.05217 15.239 7.89917 15.061C7.60517 15.0155 7.25417 14.987 6.83667 14.987C6.41917 14.987 6.06867 15.0155 5.77466 15.061C4.62167 15.239 4.15267 16.272 4.12317 17.131C4.10317 17.695 4.08667 18.5275 4.08667 19.7335C4.08667 20.0652 4.08783 20.3698 4.09017 20.6475C3.72383 20.6348 3.399 20.6225 3.11567 20.6105C2.59067 20.588 2.24267 20.2585 2.18667 19.8255C1.97317 18.183 1.99167 16.6015 2.01167 14.9105C2.01817 14.402 2.02417 13.8825 2.02417 13.349C2.02417 12.584 2.29917 11.9365 2.77367 11.544C3.79317 10.702 5.59166 9.43999 8.20066 8.41399M11.8362 15.6125C11.8362 15.3473 11.9415 15.0929 12.1291 14.9054C12.3166 14.7178 12.5709 14.6125 12.8362 14.6125H17.8362C18.1014 14.6125 18.3557 14.7178 18.5433 14.9054C18.7308 15.0929 18.8362 15.3473 18.8362 15.6125C18.8362 15.8777 18.7308 16.1321 18.5433 16.3196C18.3557 16.5071 18.1014 16.6125 17.8362 16.6125H12.8362C12.5709 16.6125 12.3166 16.5071 12.1291 16.3196C11.9415 16.1321 11.8362 15.8777 11.8362 15.6125ZM12.8362 17.485C12.5709 17.485 12.3166 17.5903 12.1291 17.7779C11.9415 17.9654 11.8362 18.2198 11.8362 18.485C11.8362 18.7502 11.9415 19.0046 12.1291 19.1921C12.3166 19.3796 12.5709 19.485 12.8362 19.485H17.8362C18.1014 19.485 18.3557 19.3796 18.5433 19.1921C18.7308 19.0046 18.8362 18.7502 18.8362 18.485C18.8362 18.2198 18.7308 17.9654 18.5433 17.7779C18.3557 17.5903 18.1014 17.485 17.8362 17.485H12.8362ZM11.2262 3.58999C11.4668 3.70158 11.6533 3.90421 11.7446 4.15331C11.8359 4.4024 11.8245 4.67756 11.7129 4.91824C11.6013 5.15893 11.3987 5.34542 11.1496 5.43671C10.9005 5.52799 10.6253 5.51658 10.3847 5.40499C9.30966 4.90649 7.93166 4.80949 6.26567 5.43399C4.16967 6.21949 2.22917 6.15899 0.603165 5.40499C0.362481 5.2934 0.175984 5.09077 0.0847006 4.84168C-0.00658277 4.59258 0.00482527 4.31743 0.116415 4.07674C0.228005 3.83606 0.430636 3.64956 0.679731 3.55828C0.928826 3.46699 1.20398 3.4784 1.44467 3.58999C2.51967 4.08849 3.89716 4.18549 5.56366 3.56099C7.66016 2.77599 9.60016 2.83599 11.2262 3.58999Z" fill="#666666"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
|
|
@ -0,0 +1,7 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#666666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.0001 3H9.0001C7.0501 8.84 7.0501 15.16 9.0001 21H8.0001" stroke="#666666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15 3C16.95 8.84 16.95 15.16 15 21" stroke="#666666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 16V15C8.84 16.95 15.16 16.95 21 15V16" stroke="#666666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 8.99961C8.84 7.04961 15.16 7.04961 21 8.99961" stroke="#666666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 876 B |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.62 20.8096C12.28 20.9296 11.72 20.9296 11.38 20.8096C8.48 19.8196 2 15.6896 2 8.68961C2 5.59961 4.49 3.09961 7.56 3.09961C9.38 3.09961 10.99 3.97961 12 5.33961C13.01 3.97961 14.63 3.09961 16.44 3.09961C19.51 3.09961 22 5.59961 22 8.68961C22 15.6896 15.52 19.8196 12.62 20.8096Z" stroke="#666666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 480 B |
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.6 3.49429C22.6747 2.87029 23.2133 2.55829 23.74 2.69962C24.268 2.84229 24.5787 3.38229 25.2 4.46362L27.176 7.90362C27.7973 8.98495 28.108 9.52629 27.9667 10.0556C27.8253 10.5863 27.2867 10.8983 26.2107 11.5223L21.6467 14.1703C20.5707 14.7943 20.0333 15.1076 19.5053 14.965C18.9787 14.8223 18.668 14.2823 18.0467 13.201L16.0707 9.76095C15.4493 8.67962 15.1373 8.13829 15.28 7.60895C15.4227 7.07962 15.96 6.76762 17.0347 6.14229L21.6 3.49429Z" stroke="#666666" stroke-width="2"/>
|
||||
<path d="M15.412 8.61328L18.7054 14.3479L14.1414 16.9973C13.0654 17.6213 12.528 17.9333 12 17.7906C11.4734 17.6493 11.1627 17.1079 10.5414 16.0279L9.88269 14.8813C9.26135 13.7999 8.94935 13.2586 9.09202 12.7293C9.23335 12.1986 9.77202 11.8866 10.8467 11.2626L15.412 8.61328Z" stroke="#666666" stroke-width="2"/>
|
||||
<path d="M9.22396 13.7334L11.2 17.1734L7.20529 19.4934C6.67463 19.8014 6.40796 19.9561 6.14796 19.9907C5.80164 20.0339 5.45219 19.9396 5.17463 19.7281C4.96663 19.5681 4.81329 19.3014 4.50529 18.7654C4.19863 18.2321 4.04529 17.9654 4.01196 17.7027C3.96656 17.355 4.05997 17.0034 4.27196 16.7241C4.43196 16.5147 4.69863 16.3614 5.22929 16.0521L9.22396 13.7334Z" stroke="#666666" stroke-width="2"/>
|
||||
<path d="M10 29.3333L16 16L22 29.3333" stroke="#666666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.60969 11.5358H5.90625C5.93111 11.5358 5.95496 11.526 5.97254 11.5084C5.99012 11.4908 6 11.4669 6 11.4421V7.82145C6 7.33834 6.19191 6.87502 6.53352 6.53341C6.87513 6.1918 7.33845 5.99989 7.82156 5.99989H11.4422C11.4671 5.99989 11.4909 5.99001 11.5085 5.97243C11.5261 5.95485 11.5359 5.931 11.5359 5.90614V4.60958C11.5359 3.33739 12.5438 2.27239 13.8159 2.25036C14.1242 2.24494 14.4304 2.30097 14.7167 2.41516C15.0031 2.52935 15.2638 2.69942 15.4837 2.91546C15.7037 3.13149 15.8783 3.38916 15.9976 3.67342C16.1169 3.95769 16.1784 4.26286 16.1784 4.57114V5.90614C16.1784 5.931 16.1883 5.95485 16.2059 5.97243C16.2235 5.99001 16.2473 5.99989 16.2722 5.99989H19.8928C20.3849 6.00137 20.8564 6.19752 21.2044 6.54549C21.5524 6.89345 21.7485 7.36498 21.75 7.85708V11.1561C21.75 11.181 21.7401 11.2048 21.7225 11.2224C21.705 11.24 21.6811 11.2499 21.6562 11.2499H20.3948C19.0181 11.2499 17.8786 12.4443 17.8594 13.821C17.8397 15.2211 18.9614 16.4999 20.3573 16.4999H21.6562C21.6811 16.4999 21.705 16.5098 21.7225 16.5273C21.7401 16.5449 21.75 16.5688 21.75 16.5936V19.8927C21.7485 20.3848 21.5524 20.8563 21.2044 21.2043C20.8564 21.5523 20.3849 21.7484 19.8928 21.7499H16.5938C16.5689 21.7499 16.545 21.74 16.5275 21.7224C16.5099 21.7048 16.5 21.681 16.5 21.6561V20.675C16.5 19.2557 15.3398 18.0336 13.9219 18.0004C12.5109 17.9675 11.25 18.9524 11.25 20.3572V21.6561C11.25 21.681 11.2401 21.7048 11.2225 21.7224C11.205 21.74 11.1811 21.7499 11.1562 21.7499H7.82156C7.33845 21.7499 6.87513 21.558 6.53352 21.2164C6.19191 20.8748 6 20.4114 6 19.9283V16.2721C6 16.2472 5.99012 16.2234 5.97254 16.2058C5.95496 16.1882 5.93111 16.1783 5.90625 16.1783H4.57125C3.27609 16.1783 2.25 15.1157 2.25 13.8158C2.25 12.516 3.3375 11.5358 4.60969 11.5358Z" stroke="#666666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.99991 10.0726C10.2923 10.0726 11.3399 9.02492 11.3399 7.73258C11.3399 6.44023 10.2923 5.39258 8.99991 5.39258C7.70757 5.39258 6.65991 6.44023 6.65991 7.73258C6.65991 9.02492 7.70757 10.0726 8.99991 10.0726Z" stroke="#666666"/>
|
||||
<path d="M2.71503 6.3675C4.19253 -0.127498 13.815 -0.119998 15.285 6.375C16.1475 10.185 13.7775 13.41 11.7 15.405C10.1925 16.86 7.80753 16.86 6.29253 15.405C4.22253 13.41 1.85253 10.1775 2.71503 6.3675Z" stroke="#666666"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 565 B |
|
|
@ -0,0 +1,58 @@
|
|||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class AiRagService {
|
||||
static const String _baseUrl =
|
||||
'https://n8n-didvan.liara.run/webhook/didvan-rag';
|
||||
static const String _authHeader =
|
||||
'Basic dXNlcjpkYXNqZHBuM3BjdTQzcDM0aWpyaA==';
|
||||
|
||||
static Future<AiRagResponse> sendMessage(String message) async {
|
||||
try {
|
||||
final response = await http.post(
|
||||
Uri.parse(_baseUrl),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': _authHeader,
|
||||
'Accept': '*/*',
|
||||
},
|
||||
body: jsonEncode({
|
||||
'chatInput': message,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body);
|
||||
return AiRagResponse(
|
||||
output: data['output'] ?? '',
|
||||
sources: List<int>.from(data['sources'] ?? []),
|
||||
isSuccess: true,
|
||||
);
|
||||
} else {
|
||||
return AiRagResponse(
|
||||
output: 'خطا در دریافت پاسخ از سرور',
|
||||
sources: [],
|
||||
isSuccess: false,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return AiRagResponse(
|
||||
output: 'خطا در ارتباط با سرور: ${e.toString()}',
|
||||
sources: [],
|
||||
isSuccess: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AiRagResponse {
|
||||
final String output;
|
||||
final List<int> sources;
|
||||
final bool isSuccess;
|
||||
|
||||
AiRagResponse({
|
||||
required this.output,
|
||||
required this.sources,
|
||||
required this.isSuccess,
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import 'dart:io';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class AiVoiceService {
|
||||
static const String _uploadUrl = 'https://n8n-didvan.liara.run/webhook/upload-mp3';
|
||||
static const String _authHeader = 'Basic dXNlcjpkYXNqZHBuM3BjdTQzcDM0aWpyaA==';
|
||||
|
||||
static Future<VoiceUploadResponse> uploadVoice(String filePath) async {
|
||||
try {
|
||||
final file = File(filePath);
|
||||
final bytes = await file.readAsBytes();
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse(_uploadUrl),
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Authorization': _authHeader,
|
||||
'Accept': '*/*',
|
||||
},
|
||||
body: bytes,
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// فرض میکنیم سرور متن رو برمیگردونه
|
||||
return VoiceUploadResponse(
|
||||
text: response.body,
|
||||
isSuccess: true,
|
||||
);
|
||||
} else {
|
||||
return VoiceUploadResponse(
|
||||
text: 'خطا در آپلود فایل صوتی',
|
||||
isSuccess: false,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return VoiceUploadResponse(
|
||||
text: 'خطا در ارتباط با سرور: ${e.toString()}',
|
||||
isSuccess: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VoiceUploadResponse {
|
||||
final String text;
|
||||
final bool isSuccess;
|
||||
|
||||
VoiceUploadResponse({
|
||||
required this.text,
|
||||
required this.isSuccess,
|
||||
});
|
||||
}
|
||||
|
|
@ -867,28 +867,14 @@ class _AiChatPageState extends State<AiChatPage> with TickerProviderStateMixin {
|
|||
? Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
16, 16, 16, 0),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 50,
|
||||
color: Colors.green,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'VIDEO PLAYER TEST',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius:
|
||||
DesignConfig.lowBorderRadius,
|
||||
child: ChatVideoPlayer(
|
||||
src: RequestHelper.baseUrl +
|
||||
file.path,
|
||||
custome: const CustomControls(),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: ClipRRect(
|
||||
borderRadius:
|
||||
DesignConfig.lowBorderRadius,
|
||||
child: ChatVideoPlayer(
|
||||
src: RequestHelper.baseUrl +
|
||||
file.path,
|
||||
custome: const CustomControls(),
|
||||
),
|
||||
),
|
||||
)
|
||||
: file.isImage()
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ class _AiSectionPageState extends State<AiSectionPage> with TickerProviderStateM
|
|||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height - 100,
|
||||
height: 500,
|
||||
child: _buildAiGrid(context, aiState),
|
||||
),
|
||||
),
|
||||
|
|
@ -391,6 +391,7 @@ class _AnimatedGridCardState extends State<_AnimatedGridCard> {
|
|||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
// ignore: deprecated_member_use
|
||||
color: Colors.black.withOpacity(_isHovered ? 0.15 : 0.05),
|
||||
blurRadius: _isHovered ? 20 : 10,
|
||||
offset: Offset(0, _isHovered ? 8 : 4),
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/home_page_content/home_page_list.dart';
|
||||
import 'package:didvan/models/home_page_content/swot.dart';
|
||||
import 'package:didvan/models/new_statistic/new_statistics_model.dart';
|
||||
import 'package:didvan/providers/user.dart';
|
||||
import 'package:didvan/routes/routes.dart';
|
||||
import 'package:didvan/views/home/explore/explore.dart';
|
||||
import 'package:didvan/views/home/main/main_page_state.dart';
|
||||
import 'package:didvan/views/home/main/widgets/main_content.dart';
|
||||
import 'package:didvan/views/home/main/widgets/story_section.dart';
|
||||
import 'package:didvan/views/home/main/widgets/simple_explore_card.dart';
|
||||
import 'package:didvan/views/home/new_statistic/new_statistics_state.dart';
|
||||
import 'package:didvan/views/widgets/didvan/card.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
|
||||
import 'package:didvan/views/widgets/carousel_3d.dart';
|
||||
import 'package:didvan/views/home/home_state.dart';
|
||||
import 'package:didvan/views/widgets/text_divider.dart';
|
||||
import 'package:didvan/views/widgets/mini_chart.dart';
|
||||
import 'package:didvan/views/widgets/home_app_bar.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -79,9 +74,9 @@ class _MainPageState extends State<MainPage> {
|
|||
const TextDivider(text: 'دیدهبان')
|
||||
.animate()
|
||||
.fadeIn(delay: 400.ms, duration: 500.ms),
|
||||
const _DidvanSignalsTitle()
|
||||
.animate()
|
||||
.fadeIn(delay: 500.ms, duration: 500.ms),
|
||||
// const _DidvanSignalsTitle()
|
||||
// .animate()
|
||||
// .fadeIn(delay: 500.ms, duration: 500.ms),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: StorySection(stories: state.stories),
|
||||
|
|
@ -105,12 +100,16 @@ class _MainPageState extends State<MainPage> {
|
|||
swotItems: state.swotItems,
|
||||
).animate().fadeIn(delay: 1000.ms, duration: 500.ms),
|
||||
],
|
||||
const _IndustryPulseTitle()
|
||||
.animate()
|
||||
.fadeIn(delay: 1100.ms, duration: 500.ms),
|
||||
const _IndustryPulseCards()
|
||||
.animate()
|
||||
.fadeIn(delay: 1200.ms, duration: 500.ms),
|
||||
if (state.swotItems.isNotEmpty)
|
||||
SwotSection(swotItems: state.swotItems)
|
||||
.animate()
|
||||
.fadeIn(delay: 1100.ms, duration: 500.ms),
|
||||
// const _IndustryPulseTitle()
|
||||
// .animate()
|
||||
// .fadeIn(delay: 1100.ms, duration: 500.ms),
|
||||
// const _IndustryPulseCards()
|
||||
// .animate()
|
||||
// .fadeIn(delay: 1200.ms, duration: 500.ms),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -121,38 +120,38 @@ class _MainPageState extends State<MainPage> {
|
|||
}
|
||||
}
|
||||
|
||||
class _DidvanSignalsTitle extends StatelessWidget {
|
||||
const _DidvanSignalsTitle();
|
||||
// class _DidvanSignalsTitle extends StatelessWidget {
|
||||
// const _DidvanSignalsTitle();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
top: 0,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'lib/assets/icons/voice-square.svg',
|
||||
color: Theme.of(context).colorScheme.title,
|
||||
width: 30,
|
||||
height: 30,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
DidvanText(
|
||||
"سیگنالهای دیدوان",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
color: const Color.fromARGB(255, 0, 89, 119),
|
||||
fontSize: 13,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Padding(
|
||||
// padding: const EdgeInsets.only(
|
||||
// left: 16,
|
||||
// right: 16,
|
||||
// bottom: 16,
|
||||
// top: 0,
|
||||
// ),
|
||||
// child: Row(
|
||||
// children: [
|
||||
// SvgPicture.asset(
|
||||
// 'lib/assets/icons/voice-square.svg',
|
||||
// color: Theme.of(context).colorScheme.title,
|
||||
// width: 30,
|
||||
// height: 30,
|
||||
// ),
|
||||
// const SizedBox(width: 5),
|
||||
// DidvanText(
|
||||
// "سیگنالهای دیدوان",
|
||||
// style: Theme.of(context).textTheme.titleMedium,
|
||||
// color: const Color.fromARGB(255, 0, 89, 119),
|
||||
// fontSize: 13,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
class _ExploreLatestTitle extends StatelessWidget {
|
||||
const _ExploreLatestTitle();
|
||||
|
|
@ -264,257 +263,257 @@ class _ExploreLatestSlider extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class _IndustryPulseTitle extends StatelessWidget {
|
||||
const _IndustryPulseTitle();
|
||||
// class _IndustryPulseTitle extends StatelessWidget {
|
||||
// const _IndustryPulseTitle();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
top: 16,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'lib/assets/icons/chart 2.svg',
|
||||
color: Theme.of(context).colorScheme.title,
|
||||
width: 30,
|
||||
height: 30,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
DidvanText(
|
||||
"نبض صنعت",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
color: const Color.fromARGB(255, 0, 89, 119),
|
||||
fontSize: 13,
|
||||
),
|
||||
],
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<HomeState>().tabController.animateTo(2);
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
DidvanText(
|
||||
"مشاهده همه",
|
||||
color: Color.fromARGB(255, 0, 126, 167),
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Padding(
|
||||
// padding: const EdgeInsets.only(
|
||||
// left: 16,
|
||||
// right: 16,
|
||||
// bottom: 16,
|
||||
// top: 16,
|
||||
// ),
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Row(
|
||||
// children: [
|
||||
// SvgPicture.asset(
|
||||
// 'lib/assets/icons/chart 2.svg',
|
||||
// color: Theme.of(context).colorScheme.title,
|
||||
// width: 30,
|
||||
// height: 30,
|
||||
// ),
|
||||
// const SizedBox(width: 5),
|
||||
// DidvanText(
|
||||
// "نبض صنعت",
|
||||
// style: Theme.of(context).textTheme.titleMedium,
|
||||
// color: const Color.fromARGB(255, 0, 89, 119),
|
||||
// fontSize: 13,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// GestureDetector(
|
||||
// onTap: () {
|
||||
// context.read<HomeState>().tabController.animateTo(2);
|
||||
// },
|
||||
// child: const Row(
|
||||
// children: [
|
||||
// DidvanText(
|
||||
// "مشاهده همه",
|
||||
// color: Color.fromARGB(255, 0, 126, 167),
|
||||
// fontWeight: FontWeight.normal,
|
||||
// fontSize: 12,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
class _IndustryPulseCards extends StatefulWidget {
|
||||
const _IndustryPulseCards();
|
||||
// class _IndustryPulseCards extends StatefulWidget {
|
||||
// const _IndustryPulseCards();
|
||||
|
||||
@override
|
||||
State<_IndustryPulseCards> createState() => _IndustryPulseCardsState();
|
||||
}
|
||||
// @override
|
||||
// State<_IndustryPulseCards> createState() => _IndustryPulseCardsState();
|
||||
// }
|
||||
|
||||
class _IndustryPulseCardsState extends State<_IndustryPulseCards> {
|
||||
late PageController _pageController;
|
||||
// class _IndustryPulseCardsState extends State<_IndustryPulseCards> {
|
||||
// late PageController _pageController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pageController = PageController(viewportFraction: 0.45);
|
||||
}
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// _pageController = PageController(viewportFraction: 0.45);
|
||||
// }
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
// @override
|
||||
// void dispose() {
|
||||
// _pageController.dispose();
|
||||
// super.dispose();
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StateHandler<NewStatisticState>(
|
||||
state: context.watch<NewStatisticState>(),
|
||||
placeholder: const Center(child: CircularProgressIndicator()),
|
||||
onRetry: () => context.read<NewStatisticState>().init(),
|
||||
builder: (context, statisticState) {
|
||||
if (statisticState.contents.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return StateHandler<NewStatisticState>(
|
||||
// state: context.watch<NewStatisticState>(),
|
||||
// placeholder: const Center(child: CircularProgressIndicator()),
|
||||
// onRetry: () => context.read<NewStatisticState>().init(),
|
||||
// builder: (context, statisticState) {
|
||||
// if (statisticState.contents.isEmpty) {
|
||||
// return const SizedBox.shrink();
|
||||
// }
|
||||
|
||||
final List<Content> allItems = [];
|
||||
statisticState.contents.forEach((category) {
|
||||
allItems.addAll(category.contents);
|
||||
});
|
||||
// final List<Content> allItems = [];
|
||||
// statisticState.contents.forEach((category) {
|
||||
// allItems.addAll(category.contents);
|
||||
// });
|
||||
|
||||
final List<String> desiredTitles = [
|
||||
'دلار',
|
||||
'بیت کوین',
|
||||
'نیکل',
|
||||
'نفت خام'
|
||||
];
|
||||
final List<Content> itemsToShow = allItems
|
||||
.where((item) => desiredTitles.contains(item.title))
|
||||
.toList();
|
||||
// final List<String> desiredTitles = [
|
||||
// 'دلار',
|
||||
// 'بیت کوین',
|
||||
// 'نیکل',
|
||||
// 'نفت خام'
|
||||
// ];
|
||||
// final List<Content> itemsToShow = allItems
|
||||
// .where((item) => desiredTitles.contains(item.title))
|
||||
// .toList();
|
||||
|
||||
if (itemsToShow.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
// if (itemsToShow.isEmpty) {
|
||||
// return const SizedBox.shrink();
|
||||
// }
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SizedBox(
|
||||
height: 165,
|
||||
child: PageView.builder(
|
||||
padEnds: false,
|
||||
controller: _pageController,
|
||||
itemCount: itemsToShow.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: _IndustryPulseCard(statistic: itemsToShow[index]),
|
||||
),
|
||||
)
|
||||
.animate()
|
||||
.fadeIn(delay: (200 * index).ms, duration: 500.ms)
|
||||
.slideX(
|
||||
begin: -0.5, duration: 500.ms, curve: Curves.easeOut);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
// return Padding(
|
||||
// padding: const EdgeInsets.only(left: 16.0),
|
||||
// child: Align(
|
||||
// alignment: Alignment.centerLeft,
|
||||
// child: SizedBox(
|
||||
// height: 165,
|
||||
// child: PageView.builder(
|
||||
// padEnds: false,
|
||||
// controller: _pageController,
|
||||
// itemCount: itemsToShow.length,
|
||||
// itemBuilder: (context, index) {
|
||||
// return Padding(
|
||||
// padding: const EdgeInsets.only(right: 8.0),
|
||||
// child: AspectRatio(
|
||||
// aspectRatio: 1,
|
||||
// child: _IndustryPulseCard(statistic: itemsToShow[index]),
|
||||
// ),
|
||||
// )
|
||||
// .animate()
|
||||
// .fadeIn(delay: (200 * index).ms, duration: 500.ms)
|
||||
// .slideX(
|
||||
// begin: -0.5, duration: 500.ms, curve: Curves.easeOut);
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
class _IndustryPulseCard extends StatelessWidget {
|
||||
final Content statistic;
|
||||
// class _IndustryPulseCard extends StatelessWidget {
|
||||
// final Content statistic;
|
||||
|
||||
const _IndustryPulseCard({required this.statistic});
|
||||
// const _IndustryPulseCard({required this.statistic});
|
||||
|
||||
Color _diffColor(BuildContext context) => statistic.data.dt == 'high'
|
||||
? Theme.of(context).colorScheme.success
|
||||
: Theme.of(context).colorScheme.error;
|
||||
// Color _diffColor(BuildContext context) => statistic.data.dt == 'high'
|
||||
// ? Theme.of(context).colorScheme.success
|
||||
// : Theme.of(context).colorScheme.error;
|
||||
|
||||
bool get _hasDiff => statistic.data.dp != 0;
|
||||
// bool get _hasDiff => statistic.data.dp != 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final state = context.read<NewStatisticState>();
|
||||
return GestureDetector(
|
||||
onTap: () =>
|
||||
Navigator.of(context).pushNamed(Routes.statisticDetails, arguments: {
|
||||
'onMarkChanged': (value) => onMarkChanged(statistic.id, value),
|
||||
'label': statistic.label,
|
||||
'title': statistic.title,
|
||||
'marked': statistic.marked,
|
||||
}).then(
|
||||
(value) => state.getStatistic(),
|
||||
),
|
||||
child: DidvanCard(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 9),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MiniChart(
|
||||
label: statistic.label,
|
||||
width: double.infinity,
|
||||
height: 38,
|
||||
lineColor: _hasDiff
|
||||
? _diffColor(context)
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
changePercent: statistic.data.dp.toDouble(),
|
||||
trend: statistic.data.dt,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (statistic.marked)
|
||||
Icon(
|
||||
Icons.star,
|
||||
color: Theme.of(context).colorScheme.yellow,
|
||||
size: 16,
|
||||
),
|
||||
if (statistic.marked) const SizedBox(width: 4),
|
||||
DidvanText(
|
||||
statistic.title,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
DidvanText(
|
||||
statistic.data.p,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
if (_hasDiff) const SizedBox(height: 6),
|
||||
if (_hasDiff)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: statistic.data.dt == 'high'
|
||||
? const Color.fromRGBO(245, 255, 252, 1)
|
||||
: const Color.fromRGBO(255, 248, 248, 1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
statistic.data.dt == 'high'
|
||||
? DidvanIcons.angle_up_regular
|
||||
: DidvanIcons.angle_down_regular,
|
||||
size: 16,
|
||||
color: _diffColor(context),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
DidvanText(
|
||||
'${statistic.data.dp}%',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
color: _diffColor(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// final state = context.read<NewStatisticState>();
|
||||
// return GestureDetector(
|
||||
// onTap: () =>
|
||||
// Navigator.of(context).pushNamed(Routes.statisticDetails, arguments: {
|
||||
// 'onMarkChanged': (value) => onMarkChanged(statistic.id, value),
|
||||
// 'label': statistic.label,
|
||||
// 'title': statistic.title,
|
||||
// 'marked': statistic.marked,
|
||||
// }).then(
|
||||
// (value) => state.getStatistic(),
|
||||
// ),
|
||||
// child: DidvanCard(
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 9),
|
||||
// child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||
// children: [
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
// child: Row(
|
||||
// children: [
|
||||
// Expanded(
|
||||
// child: MiniChart(
|
||||
// label: statistic.label,
|
||||
// width: double.infinity,
|
||||
// height: 38,
|
||||
// lineColor: _hasDiff
|
||||
// ? _diffColor(context)
|
||||
// : Theme.of(context).colorScheme.primary,
|
||||
// changePercent: statistic.data.dp.toDouble(),
|
||||
// trend: statistic.data.dt,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 20),
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// if (statistic.marked)
|
||||
// Icon(
|
||||
// Icons.star,
|
||||
// color: Theme.of(context).colorScheme.yellow,
|
||||
// size: 16,
|
||||
// ),
|
||||
// if (statistic.marked) const SizedBox(width: 4),
|
||||
// DidvanText(
|
||||
// statistic.title,
|
||||
// style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
// fontWeight: FontWeight.w600,
|
||||
// ),
|
||||
// maxLines: 1,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// const SizedBox(height: 6),
|
||||
// DidvanText(
|
||||
// statistic.data.p,
|
||||
// style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
// fontWeight: FontWeight.w500,
|
||||
// ),
|
||||
// ),
|
||||
// if (_hasDiff) const SizedBox(height: 6),
|
||||
// if (_hasDiff)
|
||||
// Container(
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
// decoration: BoxDecoration(
|
||||
// color: statistic.data.dt == 'high'
|
||||
// ? const Color.fromRGBO(245, 255, 252, 1)
|
||||
// : const Color.fromRGBO(255, 248, 248, 1),
|
||||
// borderRadius: BorderRadius.circular(4),
|
||||
// ),
|
||||
// child: Row(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// Icon(
|
||||
// statistic.data.dt == 'high'
|
||||
// ? DidvanIcons.angle_up_regular
|
||||
// : DidvanIcons.angle_down_regular,
|
||||
// size: 16,
|
||||
// color: _diffColor(context),
|
||||
// ),
|
||||
// const SizedBox(width: 4),
|
||||
// DidvanText(
|
||||
// '${statistic.data.dp}%',
|
||||
// style: Theme.of(context).textTheme.bodySmall,
|
||||
// color: _diffColor(context),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
void onMarkChanged(int id, bool value) {
|
||||
UserProvider.changeStatisticMark(id, value);
|
||||
}
|
||||
}
|
||||
// void onMarkChanged(int id, bool value) {
|
||||
// UserProvider.changeStatisticMark(id, value);
|
||||
// }
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -186,8 +186,10 @@ class InfographyItem extends StatelessWidget {
|
|||
type: 'infography',
|
||||
gestureSize: 32,
|
||||
value: liked,
|
||||
onMarkChanged: (value) => onLikedChanged(id, value, true),
|
||||
onMarkChanged: (value) =>
|
||||
onLikedChanged(id, value, true),
|
||||
likes: likes,
|
||||
unlikedColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
// lib/views/home/main/widgets/swot_item_card.dart
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
|
||||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/home_page_content/swot.dart';
|
||||
import 'package:didvan/services/app_initalizer.dart';
|
||||
import 'package:didvan/services/network/request.dart';
|
||||
import 'package:didvan/views/home/main/widgets/bookmark.dart';
|
||||
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -17,7 +13,8 @@ import 'package:url_launcher/url_launcher_string.dart';
|
|||
class SwotItemCard extends StatefulWidget {
|
||||
final SwotItem item;
|
||||
|
||||
const SwotItemCard({super.key, required this.item, this.onBookmarkChangedInList});
|
||||
const SwotItemCard(
|
||||
{super.key, required this.item, this.onBookmarkChangedInList});
|
||||
final void Function(int postId, bool isBookmarked)? onBookmarkChangedInList;
|
||||
|
||||
@override
|
||||
|
|
@ -44,6 +41,7 @@ class _SwotItemCardState extends State<SwotItemCard> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
AppInitializer.openWebLink(
|
||||
|
|
@ -53,123 +51,135 @@ class _SwotItemCardState extends State<SwotItemCard> {
|
|||
);
|
||||
},
|
||||
child: Container(
|
||||
height: 500 ,
|
||||
width: kIsWeb ? 350 : 250,
|
||||
margin: const EdgeInsets.only(right: 0),
|
||||
padding: const EdgeInsets.all(0),
|
||||
height: 200,
|
||||
margin: const EdgeInsets.symmetric(vertical: 3, horizontal: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
color: theme.cardColor,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: const Color.fromARGB(255, 184, 184, 184),
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
child: CachedNetworkImage(
|
||||
errorWidget: (context, url, error) {
|
||||
if (kDebugMode) {
|
||||
print('image fetch complete with Error: $error');
|
||||
}
|
||||
return Container(
|
||||
height: 150,
|
||||
width: kIsWeb ? 350 : 300,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).colorScheme.disabledBackground,
|
||||
),
|
||||
child: const Icon(Icons.image_not_supported_outlined),
|
||||
);
|
||||
},
|
||||
errorListener: (value) {},
|
||||
fit: BoxFit.cover,
|
||||
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
||||
httpHeaders: {
|
||||
'Authorization': 'Bearer ${RequestService.token}'
|
||||
},
|
||||
width: kIsWeb ? 350 : 300,
|
||||
height: 150,
|
||||
imageUrl: widget.item.imageUrl,
|
||||
placeholder: (context, _) => ShimmerPlaceholder(
|
||||
width: kIsWeb ? 350 : 300,
|
||||
height: 150,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// === بخش تصویر (سمت راست) ===
|
||||
CachedNetworkImage(
|
||||
imageUrl: widget.item.imageUrl,
|
||||
width: 135,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
||||
httpHeaders: {
|
||||
'Authorization': 'Bearer ${RequestService.token}'
|
||||
},
|
||||
placeholder: (context, url) => const ShimmerPlaceholder(
|
||||
width: 135,
|
||||
height: double.infinity,
|
||||
),
|
||||
errorWidget: (context, url, error) {
|
||||
if (kDebugMode) {
|
||||
print('image fetch complete with Error: $error');
|
||||
}
|
||||
return Container(
|
||||
width: 135,
|
||||
height: double.infinity,
|
||||
color: theme.colorScheme.surfaceContainerHighest,
|
||||
child: Icon(
|
||||
Icons.image_not_supported_outlined,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 0),
|
||||
child: Text(
|
||||
widget.item.title,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// === بخش جزئیات (سمت چپ) ===
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
// عنوان
|
||||
Text(
|
||||
widget.item.title,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15,
|
||||
),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// بج نوع (تهدید/فرصت)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: (widget.item.type == "THREAT"
|
||||
? Colors.red
|
||||
: Colors.green)
|
||||
.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
widget.item.type == "THREAT"
|
||||
? "lib/assets/images/features/Badge.svg"
|
||||
: "lib/assets/images/features/Badge-Green.svg",
|
||||
height: 9,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
"عدد ${widget.item.type == "THREAT"
|
||||
? "تهدید"
|
||||
: "فرصت"} : ${(widget.item.x1 * widget.item.y1).toStringAsFixed(1)}",
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: widget.item.type == "THREAT"
|
||||
? Colors.red
|
||||
: Colors.green,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// دستهبندی
|
||||
Row(
|
||||
children: [
|
||||
widget.item.type == "THREAT"
|
||||
? SvgPicture.asset(
|
||||
"lib/assets/images/features/Badge.svg")
|
||||
: SvgPicture.asset(
|
||||
"lib/assets/images/features/Badge-Green.svg"),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
const Icon(
|
||||
DidvanIcons.puzzle_light,
|
||||
size: 23,
|
||||
color: Color.fromARGB(255, 102, 102, 102),
|
||||
),
|
||||
Text(
|
||||
widget.item.type == "THREAT" ? "تهدید" : "فرصت",
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
"lib/assets/images/features/ant-design_dot-chart-outlined.svg"),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
Text(
|
||||
'عدد ${widget.item.type == "THREAT" ? "تهدید" : "فرصت"}: ${((widget.item.x1 ?? 0.0) * (widget.item.y1 ?? 0.0)).toStringAsFixed(1)}',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(DidvanIcons.puzzle_light,size: 17,),
|
||||
const SizedBox(width: 5,),
|
||||
Text(
|
||||
_getCategoryName(widget.item.category),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_getCategoryName(widget.item.category),
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: const Color.fromARGB(255, 102, 102, 102),
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
bottom: 3,
|
||||
left: 0,
|
||||
child: BookmarkIcon(postId: widget.item.id),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ class PodcastListCard extends StatelessWidget {
|
|||
right: 16,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromRGBO(235, 255, 255, 255),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: ClipRRect(
|
||||
|
|
@ -73,51 +72,59 @@ class PodcastListCard extends StatelessWidget {
|
|||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 3, 12, 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
DidvanText(
|
||||
podcast.title,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
DidvanText(
|
||||
podcast.title,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const Spacer(),
|
||||
Row(
|
||||
children: [
|
||||
SvgPicture.asset('lib/assets/icons/calendar.svg'),
|
||||
const SizedBox(width: 4),
|
||||
DidvanText(
|
||||
_formatDate(podcast.createdAt),
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: const Color.fromARGB(255, 102, 102, 102),
|
||||
const Spacer(),
|
||||
Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'lib/assets/icons/calendar.svg'),
|
||||
const SizedBox(width: 4),
|
||||
DidvanText(
|
||||
_formatDate(podcast.createdAt),
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: const Color.fromARGB(
|
||||
255, 102, 102, 102),
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 7),
|
||||
Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'lib/assets/icons/clock.svg',
|
||||
color: const Color.fromARGB(255, 102, 102, 102),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
DidvanText(
|
||||
_formatDuration(podcast.duration).toPersianDigit(),
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: const Color.fromARGB(255, 102, 102, 102),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 7),
|
||||
Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'lib/assets/icons/clock.svg',
|
||||
color:
|
||||
const Color.fromARGB(255, 102, 102, 102),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 4),
|
||||
DidvanText(
|
||||
_formatDuration(podcast.duration)
|
||||
.toPersianDigit(),
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: const Color.fromARGB(
|
||||
255, 102, 102, 102),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -135,4 +142,4 @@ class PodcastListCard extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/enums.dart';
|
||||
import 'package:didvan/models/requests/news.dart';
|
||||
import 'package:didvan/models/view/action_sheet_data.dart';
|
||||
import 'package:didvan/models/view/app_bar_data.dart';
|
||||
import 'package:didvan/utils/action_sheet.dart';
|
||||
import 'package:didvan/views/news/news_state.dart';
|
||||
import 'package:didvan/views/widgets/date_picker_button.dart';
|
||||
import 'package:didvan/views/widgets/didvan/scaffold.dart';
|
||||
import 'package:didvan/views/widgets/home_app_bar.dart';
|
||||
import 'package:didvan/views/widgets/item_title.dart';
|
||||
import 'package:didvan/views/widgets/overview/news.dart';
|
||||
import 'package:didvan/views/widgets/search_field.dart';
|
||||
import 'package:didvan/views/widgets/state_handlers/empty_result.dart';
|
||||
import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class News extends StatefulWidget {
|
||||
|
|
@ -36,31 +37,56 @@ class _NewsState extends State<News> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final state = context.watch<NewsState>();
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
return DidvanScaffold(
|
||||
padding: EdgeInsets.zero,
|
||||
appBarData: AppBarData(
|
||||
title: 'دنیای فولاد',
|
||||
hasBack: true,
|
||||
hasElevation: false,
|
||||
),
|
||||
appBarData: null,
|
||||
slivers: [
|
||||
if (state.appState != AppState.failed)
|
||||
SliverAppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
scrolledUnderElevation: 0,
|
||||
automaticallyImplyLeading: false,
|
||||
pinned: true,
|
||||
flexibleSpace: Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
|
||||
child: SearchField(
|
||||
focusNode: _focusNode,
|
||||
title: 'دنیای فولاد',
|
||||
onChanged: _onChanged,
|
||||
onFilterButtonPressed: _showFilterBottomSheet,
|
||||
isFiltered: state.isFiltering,
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: HomeAppBar(
|
||||
showBackButton: false,
|
||||
showSearchField: state.appState != AppState.failed,
|
||||
onSearchChanged: _onChanged,
|
||||
onFilterPressed: _showFilterBottomSheet,
|
||||
searchFocusNode: _focusNode,
|
||||
isFiltered: state.isFiltering,
|
||||
searchValue: state.search,
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: colorScheme.surface,
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
titleSpacing: 16,
|
||||
title: Text(
|
||||
'دنیای فولاد',
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
color: const Color.fromARGB(255, 0, 53, 70),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 19
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: SvgPicture.asset(
|
||||
'lib/assets/icons/arrow-left.svg',
|
||||
width: 30,
|
||||
height: 30,
|
||||
colorFilter: const ColorFilter.mode(
|
||||
Color.fromARGB(255, 102, 102, 102), BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 16,
|
||||
|
|
@ -70,15 +96,19 @@ class _NewsState extends State<News> {
|
|||
centerEmptyState: false,
|
||||
onRetry: () => state.getNews(page: state.page),
|
||||
state: state,
|
||||
childCount: min(state.visibleCount, state.news.length) +
|
||||
(_hasMoreItems(state) ? 1 : 0),
|
||||
builder: (context, state, index) {
|
||||
index += 2;
|
||||
if (index % 15 == 0 && state.lastPage != state.page) {
|
||||
state.getNews(page: state.page + 1);
|
||||
final currentDisplayCount = min(state.visibleCount, state.news.length);
|
||||
|
||||
if (index == currentDisplayCount && _hasMoreItems(state)) {
|
||||
return _buildLoadMoreButton(context, state);
|
||||
}
|
||||
index -= 2;
|
||||
|
||||
if (index >= state.news.length) {
|
||||
return NewsOverview.placeholder;
|
||||
}
|
||||
|
||||
final news = state.news[index];
|
||||
return NewsOverview(
|
||||
news: news,
|
||||
|
|
@ -92,12 +122,10 @@ class _NewsState extends State<News> {
|
|||
onLikedChanged: state.onLikedChanged,
|
||||
);
|
||||
},
|
||||
enableEmptyState: state.news.isEmpty,
|
||||
enableEmptyState: state.news.isEmpty && state.appState != AppState.busy,
|
||||
emptyState: EmptyResult(
|
||||
onNewSearch: () => _focusNode.requestFocus(),
|
||||
),
|
||||
childCount:
|
||||
state.news.length + (state.lastPage == state.page ? 0 : 3),
|
||||
itemPadding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
|
||||
placeholder: NewsOverview.placeholder,
|
||||
),
|
||||
|
|
@ -105,6 +133,59 @@ class _NewsState extends State<News> {
|
|||
);
|
||||
}
|
||||
|
||||
bool _hasMoreItems(NewsState state) {
|
||||
return state.news.length > state.visibleCount || state.page < state.lastPage;
|
||||
}
|
||||
|
||||
Widget _buildLoadMoreButton(BuildContext context, NewsState state) {
|
||||
if (state.appState == AppState.busy && state.news.length <= state.visibleCount) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
|
||||
child: Center(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
state.loadMore();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 50.0,
|
||||
vertical: 12.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(255, 0, 126, 167),
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'lib/assets/icons/element-plus.svg',
|
||||
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
width: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'بارگذاری بیشتر',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onChanged(String value) {
|
||||
final state = context.read<NewsState>();
|
||||
if (value.length < 3 && value.isNotEmpty || state.lastSearch == value) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ class NewsState extends CoreProvier {
|
|||
String? endDate;
|
||||
int page = 1;
|
||||
int lastPage = 0;
|
||||
int visibleCount = 4;
|
||||
|
||||
final List<OverviewData> news = [];
|
||||
|
||||
|
|
@ -23,14 +24,24 @@ class NewsState extends CoreProvier {
|
|||
Future.delayed(Duration.zero, () {
|
||||
getNews(page: 1);
|
||||
});
|
||||
visibleCount = 4;
|
||||
}
|
||||
|
||||
void resetFilters() {
|
||||
startDate = null;
|
||||
endDate = null;
|
||||
visibleCount = 4;
|
||||
getNews(page: 1);
|
||||
}
|
||||
|
||||
void loadMore() {
|
||||
visibleCount += 4;
|
||||
if (visibleCount >= news.length && page < lastPage) {
|
||||
getNews(page: page + 1);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getNews({
|
||||
required int page,
|
||||
}) async {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// ignore_for_file: deprecated_member_use
|
||||
// ignore_for_file: deprecated_member_use, undefined_prefixed_name
|
||||
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ import 'package:universal_html/html.dart' as html;
|
|||
class StudioDetails extends StatefulWidget {
|
||||
final Map<String, dynamic> pageData;
|
||||
|
||||
const StudioDetails({Key? key, required this.pageData}) : super(key: key);
|
||||
const StudioDetails({super.key, required this.pageData});
|
||||
|
||||
@override
|
||||
State<StudioDetails> createState() => _StudioDetailsState();
|
||||
|
|
@ -43,11 +43,13 @@ class _StudioDetailsState extends State<StudioDetails> {
|
|||
state: state,
|
||||
onRetry: () => state.getStudioDetails(state.studio.id),
|
||||
builder: (context, state) {
|
||||
final String viewType = 'video-iframe-${state.studio.id}';
|
||||
|
||||
if (state.studio.type == 'video') {
|
||||
debugPrint("Playing video from URL: ${state.studio.link}");
|
||||
// ignore: undefined_prefixed_name
|
||||
|
||||
ui.platformViewRegistry.registerViewFactory(
|
||||
"video",
|
||||
viewType,
|
||||
(int viewId) => html.IFrameElement()
|
||||
..allowFullscreen = true
|
||||
..src = Uri.dataFromString(
|
||||
|
|
@ -98,7 +100,7 @@ class _StudioDetailsState extends State<StudioDetails> {
|
|||
AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: HtmlElementView(
|
||||
viewType: 'video',
|
||||
viewType: viewType,
|
||||
key: ValueKey(state.studio.id),
|
||||
),
|
||||
),
|
||||
|
|
@ -121,4 +123,4 @@ class _StudioDetailsState extends State<StudioDetails> {
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,927 @@
|
|||
import 'package:didvan/services/ai_rag_service.dart';
|
||||
import 'package:didvan/services/ai_voice_service.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/providers/user.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:record/record.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'dart:ui';
|
||||
import 'dart:io';
|
||||
|
||||
class AiChatDialog extends StatefulWidget {
|
||||
const AiChatDialog({super.key});
|
||||
|
||||
@override
|
||||
State<AiChatDialog> createState() => _AiChatDialogState();
|
||||
}
|
||||
|
||||
class _AiChatDialogState extends State<AiChatDialog>
|
||||
with TickerProviderStateMixin {
|
||||
final TextEditingController _messageController = TextEditingController();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final List<ChatMessage> _messages = [];
|
||||
bool _isLoading = false;
|
||||
bool _isRecording = false;
|
||||
late AnimationController _animationController;
|
||||
late AnimationController _pulseController;
|
||||
final AudioRecorder _audioRecorder = AudioRecorder();
|
||||
String? _recordingPath;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
);
|
||||
_pulseController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
)..repeat(reverse: true);
|
||||
|
||||
_animationController.forward();
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_messages.add(ChatMessage(
|
||||
text:
|
||||
'سلام! 👋\n\nمن دستیار هوشمند دیدوان هستم. میتونم در مورد اخبار، تحلیلها و محتوای دیدوان بهتون کمک کنم.\n\nچه سوالی دارید؟ 😊',
|
||||
isUser: false,
|
||||
timestamp: DateTime.now(),
|
||||
));
|
||||
});
|
||||
_scrollToBottom();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_messageController.dispose();
|
||||
_scrollController.dispose();
|
||||
_animationController.dispose();
|
||||
_pulseController.dispose();
|
||||
_audioRecorder.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _scrollToBottom() {
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.animateTo(
|
||||
_scrollController.position.maxScrollExtent,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _sendMessage() async {
|
||||
final message = _messageController.text.trim();
|
||||
if (message.isEmpty) return;
|
||||
|
||||
setState(() {
|
||||
_messages.add(ChatMessage(
|
||||
text: message,
|
||||
isUser: true,
|
||||
timestamp: DateTime.now(),
|
||||
));
|
||||
_isLoading = true;
|
||||
_messageController.clear();
|
||||
});
|
||||
|
||||
_scrollToBottom();
|
||||
|
||||
final response = await AiRagService.sendMessage(message);
|
||||
|
||||
setState(() {
|
||||
_messages.add(ChatMessage(
|
||||
text: response.output,
|
||||
isUser: false,
|
||||
timestamp: DateTime.now(),
|
||||
sources: response.sources,
|
||||
));
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
_scrollToBottom();
|
||||
}
|
||||
|
||||
Future<void> _startRecording() async {
|
||||
try {
|
||||
if (await _audioRecorder.hasPermission()) {
|
||||
setState(() {
|
||||
_isRecording = true;
|
||||
});
|
||||
|
||||
final directory = await getTemporaryDirectory();
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
_recordingPath = '${directory.path}/voice_$timestamp.m4a';
|
||||
|
||||
await _audioRecorder.start(
|
||||
const RecordConfig(
|
||||
encoder: AudioEncoder.aacLc,
|
||||
),
|
||||
path: _recordingPath!,
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_isRecording = false;
|
||||
});
|
||||
debugPrint('Error starting recording: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _stopRecording() async {
|
||||
try {
|
||||
final path = await _audioRecorder.stop();
|
||||
|
||||
setState(() {
|
||||
_isRecording = false;
|
||||
});
|
||||
|
||||
if (path != null) {
|
||||
setState(() {
|
||||
_messages.add(ChatMessage(
|
||||
text: '🎤 پیام صوتی',
|
||||
isUser: true,
|
||||
timestamp: DateTime.now(),
|
||||
));
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
_scrollToBottom();
|
||||
|
||||
final response = await AiVoiceService.uploadVoice(path);
|
||||
|
||||
if (response.isSuccess && response.text.isNotEmpty) {
|
||||
final ragResponse = await AiRagService.sendMessage(response.text);
|
||||
|
||||
setState(() {
|
||||
_messages.add(ChatMessage(
|
||||
text: ragResponse.output,
|
||||
isUser: false,
|
||||
timestamp: DateTime.now(),
|
||||
sources: ragResponse.sources,
|
||||
));
|
||||
_isLoading = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_messages.add(ChatMessage(
|
||||
text: 'خطا در پردازش پیام صوتی',
|
||||
isUser: false,
|
||||
timestamp: DateTime.now(),
|
||||
));
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_scrollToBottom();
|
||||
|
||||
try {
|
||||
await File(path).delete();
|
||||
} catch (e) {
|
||||
debugPrint('Error deleting temp file: $e');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_isRecording = false;
|
||||
_isLoading = false;
|
||||
});
|
||||
debugPrint('Error stopping recording: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
insetPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
||||
child: ScaleTransition(
|
||||
scale: CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.elasticOut,
|
||||
),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 500, maxHeight: 600),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Colors.white.withOpacity(0.95),
|
||||
const Color(0xFFF8F9FF).withOpacity(0.95),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.6),
|
||||
width: 1.5,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF0066AA).withOpacity(0.15),
|
||||
blurRadius: 30,
|
||||
spreadRadius: 0,
|
||||
offset: const Offset(0, 15),
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 15,
|
||||
spreadRadius: -3,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildHeader(context),
|
||||
Expanded(
|
||||
child: _buildMessageList(),
|
||||
),
|
||||
if (_isLoading) _buildLoadingIndicator(),
|
||||
if (_isRecording) _buildRecordingIndicator(),
|
||||
_buildInputField(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 14),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Color(0xFF0066AA),
|
||||
Color(0xFF0088DD),
|
||||
Color(0xFF00AAFF),
|
||||
],
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF0066AA).withOpacity(0.2),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
AnimatedBuilder(
|
||||
animation: _pulseController,
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.white
|
||||
.withOpacity(0.25 * _pulseController.value),
|
||||
blurRadius: 15 + (8 * _pulseController.value),
|
||||
spreadRadius: 1 + (2 * _pulseController.value),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// Avatar
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.15),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: SvgPicture.asset(
|
||||
'lib/assets/icons/live ai.svg',
|
||||
colorFilter: const ColorFilter.mode(
|
||||
Color(0xFF0066AA),
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Online indicator
|
||||
Positioned(
|
||||
bottom: 1,
|
||||
left: 1,
|
||||
child: Container(
|
||||
width: 11,
|
||||
height: 11,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF00FF88),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 1.5),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF00FF88).withOpacity(0.4),
|
||||
blurRadius: 6,
|
||||
spreadRadius: 0.5,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DidvanText(
|
||||
'دستیار هوشمند دیدوان',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
SizedBox(height: 3),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(width: 4),
|
||||
DidvanText(
|
||||
'آنلاین',
|
||||
fontSize: 11,
|
||||
color: Colors.white70,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.close_rounded,
|
||||
color: Colors.white, size: 20),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
splashRadius: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMessageList() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.white.withOpacity(0.5),
|
||||
const Color(0xFFF8F9FF).withOpacity(0.3),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.fromLTRB(14, 14, 14, 10),
|
||||
itemCount: _messages.length,
|
||||
itemBuilder: (context, index) {
|
||||
final message = _messages[index];
|
||||
return _buildMessageBubble(message);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMessageBubble(ChatMessage message) {
|
||||
return TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0.0, end: 1.0),
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeOutCubic,
|
||||
builder: (context, value, child) {
|
||||
return Transform.translate(
|
||||
offset: Offset(0, 20 * (1 - value)),
|
||||
child: Opacity(
|
||||
opacity: value,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
message.isUser ? MainAxisAlignment.start : MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (message.isUser) _buildUserAvatar(),
|
||||
if (message.isUser) const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: message.isUser
|
||||
? CrossAxisAlignment.start
|
||||
: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
gradient: !message.isUser
|
||||
? const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Color(0xFF0066AA),
|
||||
Color(0xFF0088DD),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
color: !message.isUser ? null : const Color(0xFFF5F7FA),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(!message.isUser ? 4 : 16),
|
||||
topRight: const Radius.circular(16),
|
||||
bottomLeft: const Radius.circular(16),
|
||||
bottomRight: Radius.circular(!message.isUser ? 16 : 4),
|
||||
),
|
||||
border: !message.isUser
|
||||
? null
|
||||
: Border.all(
|
||||
color: const Color(0xFFE0E5EC).withOpacity(0.5),
|
||||
width: 1,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: !message.isUser
|
||||
? const Color(0xFF0066AA).withOpacity(0.2)
|
||||
: Colors.black.withOpacity(0.03),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: DidvanText(
|
||||
message.text,
|
||||
color: !message.isUser
|
||||
? Colors.white
|
||||
: const Color(0xFF1A1A1A),
|
||||
fontSize: 13.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 3),
|
||||
child: DidvanText(
|
||||
_formatTime(message.timestamp),
|
||||
fontSize: 10,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
if (message.sources.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
const Color(0xFF0066AA).withOpacity(0.08),
|
||||
const Color(0xFF0088DD).withOpacity(0.05),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: const Color(0xFF0066AA).withOpacity(0.2),
|
||||
width: 0.8,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF0066AA).withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.bookmark_rounded,
|
||||
size: 12,
|
||||
color: Color(0xFF0066AA),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
DidvanText(
|
||||
'منابع: ${message.sources.join(", ")}',
|
||||
fontSize: 11,
|
||||
color: const Color(0xFF0066AA),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!message.isUser) const SizedBox(width: 8),
|
||||
if (!message.isUser) _buildAiAvatar(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAiAvatar() {
|
||||
return Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Color(0xFF0066AA),
|
||||
Color(0xFF00AAFF),
|
||||
],
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF0066AA).withOpacity(0.3),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.all(7),
|
||||
child: SvgPicture.asset(
|
||||
'lib/assets/icons/live ai.svg',
|
||||
colorFilter: const ColorFilter.mode(
|
||||
Colors.white,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUserAvatar() {
|
||||
return Consumer<UserProvider>(
|
||||
builder: (context, userProvider, _) {
|
||||
if (userProvider.user.photo != null &&
|
||||
userProvider.user.photo!.isNotEmpty) {
|
||||
return Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.25),
|
||||
blurRadius: 6,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Image.network(
|
||||
userProvider.user.photo!,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return _buildDefaultUserAvatar();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return _buildDefaultUserAvatar();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDefaultUserAvatar() {
|
||||
return Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Colors.grey.shade400,
|
||||
Colors.grey.shade500,
|
||||
],
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.25),
|
||||
blurRadius: 6,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.person_rounded,
|
||||
size: 18,
|
||||
color: Colors.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingIndicator() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 6, 14, 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF5F7FA),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: const Color(0xFFE0E5EC).withOpacity(0.5),
|
||||
width: 1,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.03),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'در حال تایپ...',
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
fontSize: 12,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const SpinKitThreeBounce(
|
||||
color: Color(0xFF0066AA),
|
||||
size: 14,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildAiAvatar(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRecordingIndicator() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
margin: const EdgeInsets.only(bottom: 6),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
const Color(0xFFFF3366).withOpacity(0.1),
|
||||
const Color(0xFFFF6699).withOpacity(0.05),
|
||||
],
|
||||
),
|
||||
border: Border.all(
|
||||
color: const Color(0xFFFF3366).withOpacity(0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFF3366),
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFFFF3366).withOpacity(0.4),
|
||||
blurRadius: 6,
|
||||
spreadRadius: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const Expanded(
|
||||
child: DidvanText(
|
||||
'🎙️ در حال ضبط...',
|
||||
fontSize: 12,
|
||||
color: Color(0xFFFF3366),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const Icon(
|
||||
Icons.mic_rounded,
|
||||
color: Color(0xFFFF3366),
|
||||
size: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInputField() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.95),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.06),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, -3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxHeight: 100),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF5F7FA),
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
border: Border.all(
|
||||
color: const Color(0xFFE0E5EC).withOpacity(0.5),
|
||||
width: 1.2,
|
||||
),
|
||||
),
|
||||
child: TextField(
|
||||
controller: _messageController,
|
||||
maxLines: null,
|
||||
textInputAction: TextInputAction.send,
|
||||
onSubmitted: (_) => _sendMessage(),
|
||||
style: const TextStyle(
|
||||
fontSize: 13.5,
|
||||
color: Color(0xFF1A1A1A),
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'پیام خود را بنویسید...',
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.grey.shade400,
|
||||
fontSize: 13,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (!_isLoading) {
|
||||
if (_isRecording) {
|
||||
_stopRecording();
|
||||
} else {
|
||||
_startRecording();
|
||||
}
|
||||
}
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: _isRecording
|
||||
? [const Color(0xFFFF3366), const Color(0xFFFF6699)]
|
||||
: [const Color(0xFF6B7280), const Color(0xFF9CA3AF)],
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: (_isRecording
|
||||
? const Color(0xFFFF3366)
|
||||
: const Color(0xFF6B7280))
|
||||
// ignore: deprecated_member_use
|
||||
.withOpacity(0.35),
|
||||
blurRadius: _isRecording ? 16 : 10,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(
|
||||
_isRecording ? Icons.stop_rounded : Icons.mic_rounded,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
// دکمه ارسال
|
||||
GestureDetector(
|
||||
onTap: _isLoading || _isRecording ? null : _sendMessage,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: (_isLoading || _isRecording)
|
||||
? [Colors.grey.shade300, Colors.grey.shade400]
|
||||
: [const Color(0xFF0066AA), const Color(0xFF00AAFF)],
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ((_isLoading || _isRecording)
|
||||
? Colors.grey.shade400
|
||||
: const Color(0xFF0066AA))
|
||||
// ignore: deprecated_member_use
|
||||
.withOpacity(0.35),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: _isLoading
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(11),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: const Icon(
|
||||
Icons.arrow_upward_rounded,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatTime(DateTime time) {
|
||||
final hour = time.hour.toString().padLeft(2, '0');
|
||||
final minute = time.minute.toString().padLeft(2, '0');
|
||||
return '$hour:$minute';
|
||||
}
|
||||
}
|
||||
|
||||
class ChatMessage {
|
||||
final String text;
|
||||
final bool isUser;
|
||||
final DateTime timestamp;
|
||||
final List<int> sources;
|
||||
|
||||
ChatMessage({
|
||||
required this.text,
|
||||
required this.isUser,
|
||||
required this.timestamp,
|
||||
this.sources = const [],
|
||||
});
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||
class BookmarkButton extends StatefulWidget {
|
||||
final bool value;
|
||||
final Color? color;
|
||||
final Color? unbookmarkedColor;
|
||||
final void Function(bool value) onMarkChanged;
|
||||
final bool askForConfirmation;
|
||||
final double gestureSize;
|
||||
|
|
@ -26,6 +27,7 @@ class BookmarkButton extends StatefulWidget {
|
|||
required this.itemId,
|
||||
this.askForConfirmation = false,
|
||||
this.color,
|
||||
this.unbookmarkedColor,
|
||||
this.svgIconOn,
|
||||
this.svgIconOff,
|
||||
}) : super(key: key);
|
||||
|
|
@ -73,13 +75,21 @@ class _BookmarkButtonState extends State<BookmarkButton> {
|
|||
}
|
||||
}
|
||||
|
||||
Color? _getColor(BuildContext context) {
|
||||
if (_value) {
|
||||
return widget.color;
|
||||
} else {
|
||||
return widget.unbookmarkedColor;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
print("BookmarkButton build - value: $_value");
|
||||
|
||||
|
||||
final iconOn = widget.svgIconOn ?? 'lib/assets/icons/bookmark_on.svg';
|
||||
final iconOff = widget.svgIconOff ?? 'lib/assets/icons/bookmark_off.svg';
|
||||
|
||||
|
||||
return IconButton(
|
||||
iconSize: widget.gestureSize,
|
||||
onPressed: _handleTap,
|
||||
|
|
@ -93,8 +103,9 @@ class _BookmarkButtonState extends State<BookmarkButton> {
|
|||
key: ValueKey('bookmark_$_value'),
|
||||
width: widget.gestureSize,
|
||||
height: widget.gestureSize,
|
||||
color: _getColor(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ class DidvanBNB extends StatelessWidget {
|
|||
height: 72,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius:
|
||||
const BorderRadius.vertical(top: Radius.circular(0)),
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(0)),
|
||||
border: const Border(
|
||||
top: BorderSide(
|
||||
color: Color.fromARGB(255, 224, 224, 224),
|
||||
|
|
@ -27,55 +26,61 @@ class DidvanBNB extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
_NavBarItem(
|
||||
isSelected: currentTabIndex == 1,
|
||||
title: 'رسانه',
|
||||
selectedIconPath: 'lib/assets/icons/media selected.svg', // Selected SVG icon
|
||||
unselectedIconPath: 'lib/assets/icons/media.svg', // Unselected SVG icon
|
||||
onTap: () => onTabChanged(1),
|
||||
),
|
||||
_NavBarItem(
|
||||
isSelected: currentTabIndex == 4,
|
||||
title: 'هوشان',
|
||||
selectedIconPath: 'lib/assets/icons/houshan_selected.svg', // Selected SVG icon
|
||||
unselectedIconPath: 'lib/assets/icons/bot.svg', // Unselected SVG icon
|
||||
onTap: () => onTabChanged(4),
|
||||
),
|
||||
_NavBarItem(
|
||||
isSelected: currentTabIndex == 0,
|
||||
title: 'خانه',
|
||||
selectedIconPath: DesignConfig.isDark
|
||||
? 'lib/assets/icons/selected home.svg'
|
||||
: 'lib/assets/icons/selected home.svg',
|
||||
unselectedIconPath: DesignConfig.isDark
|
||||
? 'assets/images/logos/logo-vertical-dark.svg'
|
||||
: 'lib/assets/icons/home2.svg', // Unselected SVG icon
|
||||
onTap: () => onTabChanged(0),
|
||||
),
|
||||
_NavBarItem(
|
||||
isSelected: currentTabIndex == 2,
|
||||
title: 'نبض صنعت',
|
||||
selectedIconPath: DesignConfig.isDark
|
||||
? 'lib/assets/icons/stats_nav_icon_dark_solid.svg'
|
||||
: 'lib/assets/icons/chart 2_solid.svg', // Selected SVG icon
|
||||
unselectedIconPath: DesignConfig.isDark
|
||||
? 'lib/assets/icons/stats_nav_icon_dark.svg'
|
||||
: 'lib/assets/icons/chart 2.svg', // Unselected SVG icon
|
||||
onTap: () => onTabChanged(2),
|
||||
),
|
||||
_NavBarItem(
|
||||
isSelected: currentTabIndex == 3,
|
||||
title: 'کاوش',
|
||||
selectedIconPath: 'lib/assets/icons/explore select.svg', // Selected SVG icon
|
||||
unselectedIconPath: 'lib/assets/icons/discover.svg', // Unselected SVG icon
|
||||
onTap: () => onTabChanged(3),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
_NavBarItem(
|
||||
isSelected: currentTabIndex == 1,
|
||||
title: 'رسانه',
|
||||
selectedIconPath:
|
||||
'lib/assets/icons/media selected.svg', // Selected SVG icon
|
||||
unselectedIconPath:
|
||||
'lib/assets/icons/media.svg', // Unselected SVG icon
|
||||
onTap: () => onTabChanged(1),
|
||||
),
|
||||
_NavBarItem(
|
||||
isSelected: currentTabIndex == 4,
|
||||
title: 'هوشان',
|
||||
selectedIconPath:
|
||||
'lib/assets/icons/houshan_selected.svg', // Selected SVG icon
|
||||
unselectedIconPath:
|
||||
'lib/assets/icons/bot.svg', // Unselected SVG icon
|
||||
onTap: () => onTabChanged(4),
|
||||
),
|
||||
_NavBarItem(
|
||||
isSelected: currentTabIndex == 0,
|
||||
title: 'خانه',
|
||||
selectedIconPath: DesignConfig.isDark
|
||||
? 'lib/assets/icons/selected home.svg'
|
||||
: 'lib/assets/icons/selected home.svg',
|
||||
unselectedIconPath: DesignConfig.isDark
|
||||
? 'lib/assets/icons/home2.svg'
|
||||
: 'lib/assets/icons/home2.svg', // Unselected SVG icon
|
||||
onTap: () => onTabChanged(0),
|
||||
),
|
||||
_NavBarItem(
|
||||
isSelected: currentTabIndex == 2,
|
||||
title: 'نبض صنعت',
|
||||
selectedIconPath: DesignConfig.isDark
|
||||
? 'lib/assets/icons/Nabz_Sanat.svg'
|
||||
: 'lib/assets/icons/Nabz_Sanat.svg', // Selected SVG icon
|
||||
unselectedIconPath: DesignConfig.isDark
|
||||
? 'lib/assets/icons/chart 2.svg'
|
||||
: 'lib/assets/icons/chart 2.svg', // Unselected SVG icon
|
||||
onTap: () => onTabChanged(2),
|
||||
),
|
||||
_NavBarItem(
|
||||
isSelected: currentTabIndex == 3,
|
||||
title: 'کاوش',
|
||||
selectedIconPath:
|
||||
'lib/assets/icons/explore select.svg', // Selected SVG icon
|
||||
unselectedIconPath:
|
||||
'lib/assets/icons/discover.svg', // Unselected SVG icon
|
||||
onTap: () => onTabChanged(3),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,22 +120,22 @@ class _NavBarItemState extends State<_NavBarItem>
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
|
||||
_scaleController = AnimationController(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
|
||||
_bounceController = AnimationController(
|
||||
duration: const Duration(milliseconds: 600),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
|
||||
_rippleController = AnimationController(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
|
||||
_scaleAnimation = Tween<double>(
|
||||
begin: 1.0,
|
||||
end: 0.85,
|
||||
|
|
@ -138,7 +143,7 @@ class _NavBarItemState extends State<_NavBarItem>
|
|||
parent: _scaleController,
|
||||
curve: Curves.easeInOut,
|
||||
));
|
||||
|
||||
|
||||
_bounceAnimation = Tween<double>(
|
||||
begin: 1.0,
|
||||
end: 1.2,
|
||||
|
|
@ -146,7 +151,7 @@ class _NavBarItemState extends State<_NavBarItem>
|
|||
parent: _bounceController,
|
||||
curve: Curves.elasticOut,
|
||||
));
|
||||
|
||||
|
||||
_rippleAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
|
|
@ -178,13 +183,13 @@ class _NavBarItemState extends State<_NavBarItem>
|
|||
});
|
||||
_scaleController.reverse();
|
||||
_rippleController.reverse();
|
||||
|
||||
|
||||
if (widget.isSelected) {
|
||||
_bounceController.forward().then((_) {
|
||||
_bounceController.reverse();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
widget.onTap();
|
||||
}
|
||||
|
||||
|
|
@ -223,9 +228,10 @@ class _NavBarItemState extends State<_NavBarItem>
|
|||
height: 60 * _rippleAnimation.value,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(
|
||||
0.1 * (1 - _rippleAnimation.value),
|
||||
),
|
||||
color:
|
||||
Theme.of(context).colorScheme.primary.withOpacity(
|
||||
0.1 * (1 - _rippleAnimation.value),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -234,27 +240,33 @@ class _NavBarItemState extends State<_NavBarItem>
|
|||
children: [
|
||||
const SizedBox(height: 4),
|
||||
AnimatedBuilder(
|
||||
animation: Listenable.merge([_scaleAnimation, _bounceAnimation]),
|
||||
animation:
|
||||
Listenable.merge([_scaleAnimation, _bounceAnimation]),
|
||||
builder: (context, child) {
|
||||
final scale = _scaleAnimation.value *
|
||||
final scale = _scaleAnimation.value *
|
||||
(widget.isSelected ? _bounceAnimation.value : 1.0);
|
||||
|
||||
|
||||
return Transform.scale(
|
||||
scale: scale,
|
||||
child: AnimatedContainer(
|
||||
padding: EdgeInsets.all(widget.isHomeButton ? 8 : 4),
|
||||
padding:
|
||||
EdgeInsets.all(widget.isHomeButton ? 8 : 4),
|
||||
duration: DesignConfig.lowAnimationDuration,
|
||||
|
||||
decoration:
|
||||
BoxDecoration(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: widget.isSelected
|
||||
? Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.1)
|
||||
: Colors.transparent,
|
||||
boxShadow: widget.isSelected
|
||||
? [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.3),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.3),
|
||||
blurRadius: 8,
|
||||
spreadRadius: 1,
|
||||
)
|
||||
|
|
@ -262,21 +274,28 @@ class _NavBarItemState extends State<_NavBarItem>
|
|||
: null,
|
||||
),
|
||||
child: SizedBox(
|
||||
width: widget.isHomeButton ? 50 : (widget.isSelected ? 50 : 32),
|
||||
height: widget.isHomeButton ? 50 : (widget.isSelected ? 50 : 32),
|
||||
width: widget.isHomeButton
|
||||
? 50
|
||||
: (widget.isSelected ? 50 : 32),
|
||||
height: widget.isHomeButton
|
||||
? 50
|
||||
: (widget.isSelected ? 50 : 32),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
transitionBuilder: (child, animation) {
|
||||
return ScaleTransition(
|
||||
scale: animation,
|
||||
child: RotationTransition(
|
||||
turns: Tween<double>(begin: 0.8, end: 1.0).animate(animation),
|
||||
turns: Tween<double>(begin: 0.8, end: 1.0)
|
||||
.animate(animation),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
widget.isSelected ? widget.selectedIconPath : widget.unselectedIconPath,
|
||||
widget.isSelected
|
||||
? widget.selectedIconPath
|
||||
: widget.unselectedIconPath,
|
||||
key: ValueKey(widget.isSelected),
|
||||
),
|
||||
),
|
||||
|
|
@ -310,4 +329,4 @@ class _NavBarItemState extends State<_NavBarItem>
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class DidvanCard extends StatelessWidget {
|
|||
padding: padding,
|
||||
margin: margin,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: DesignConfig.mediumBorderRadius,
|
||||
borderRadius: DesignConfig.highBorderRadius,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
border: enableBorder ? DesignConfig.cardBorder : null,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:didvan/constants/app_icons.dart';
|
|||
import 'package:didvan/views/widgets/didvan/checkbox.dart';
|
||||
import 'package:didvan/views/widgets/item_title.dart';
|
||||
import 'package:didvan/views/widgets/date_picker_button.dart';
|
||||
import 'package:didvan/views/widgets/ai_chat_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
|
|
@ -18,16 +19,29 @@ class HomeAppBar extends StatelessWidget {
|
|||
final bool showBackButton;
|
||||
final String? title;
|
||||
final bool showSearchField;
|
||||
final Function(String)? onSearchChanged;
|
||||
final VoidCallback? onFilterPressed;
|
||||
final FocusNode? searchFocusNode;
|
||||
final bool? isFiltered;
|
||||
final String? searchValue;
|
||||
|
||||
const HomeAppBar({
|
||||
super.key,
|
||||
this.showBackButton = false,
|
||||
this.title,
|
||||
this.showSearchField = false,
|
||||
this.onSearchChanged,
|
||||
this.onFilterPressed,
|
||||
this.searchFocusNode,
|
||||
this.isFiltered,
|
||||
this.searchValue,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final homeState =
|
||||
(onSearchChanged == null) ? context.watch<HomeState>() : null;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
|
|
@ -81,9 +95,10 @@ class HomeAppBar extends StatelessWidget {
|
|||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(DidvanIcons.bookmark_regular), //
|
||||
icon: SvgPicture.asset(
|
||||
'lib/assets/icons/hugeicons_telescope-01.svg'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(Routes.bookmarks); //
|
||||
Navigator.of(context).pushNamed(Routes.bookmarks);
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
|
|
@ -114,22 +129,37 @@ class HomeAppBar extends StatelessWidget {
|
|||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
child: SearchField(
|
||||
title: 'دیدوان',
|
||||
focusNode: context.read<HomeState>().searchFieldFocusNode,
|
||||
title: title ?? 'دیدوان',
|
||||
focusNode: searchFocusNode ??
|
||||
context.read<HomeState>().searchFieldFocusNode,
|
||||
onChanged: (value) {
|
||||
final homeState = context.read<HomeState>();
|
||||
if (value.length >= 2) {
|
||||
homeState.onSearchChanged(value);
|
||||
} else if (value.isEmpty) {
|
||||
homeState.clearSearch();
|
||||
if (onSearchChanged != null) {
|
||||
onSearchChanged!(value);
|
||||
} else {
|
||||
final state = context.read<HomeState>();
|
||||
if (value.length >= 2) {
|
||||
state.onSearchChanged(value);
|
||||
} else if (value.isEmpty) {
|
||||
state.clearSearch();
|
||||
}
|
||||
}
|
||||
},
|
||||
onFilterButtonPressed: () => _showFilterBottomSheet(context),
|
||||
isFiltered: context.watch<HomeState>().filtering,
|
||||
value: context.watch<HomeState>().search,
|
||||
extraIconPath: 'lib/assets/icons/profile.svg',
|
||||
onFilterButtonPressed: () {
|
||||
if (onFilterPressed != null) {
|
||||
onFilterPressed!();
|
||||
} else {
|
||||
_showFilterBottomSheet(context);
|
||||
}
|
||||
},
|
||||
isFiltered: isFiltered ?? homeState?.filtering ?? false,
|
||||
value: searchValue ?? homeState?.search,
|
||||
extraIconPath: 'lib/assets/icons/live ai.svg',
|
||||
onExtraIconPressed: () {
|
||||
print('Extra icon pressed!');
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (context) => const AiChatDialog(),
|
||||
);
|
||||
},
|
||||
),
|
||||
).animate().fadeIn(delay: 200.ms, duration: 500.ms),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:flutter_svg/svg.dart';
|
|||
class LikedButton extends StatefulWidget {
|
||||
final bool value;
|
||||
final Color? color;
|
||||
final Color? unlikedColor;
|
||||
final void Function(bool value) onMarkChanged;
|
||||
final bool askForConfirmation;
|
||||
final double gestureSize;
|
||||
|
|
@ -25,6 +26,7 @@ class LikedButton extends StatefulWidget {
|
|||
required this.likes,
|
||||
this.askForConfirmation = false,
|
||||
this.color,
|
||||
this.unlikedColor,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -92,10 +94,14 @@ class _LikedButtonState extends State<LikedButton> {
|
|||
}
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
_value ? 'lib/assets/icons/heart_fill.svg':'lib/assets/icons/heart.svg',
|
||||
_value
|
||||
? 'lib/assets/icons/heart_fill.svg'
|
||||
: 'lib/assets/icons/heart2.svg',
|
||||
height: 24,
|
||||
color: widget.color ??
|
||||
(!_value ? Colors.white : Theme.of(context).colorScheme.error),
|
||||
(_value
|
||||
? Theme.of(context).colorScheme.error
|
||||
: widget.unlikedColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:didvan/views/widgets/liked_button.dart';
|
|||
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:persian_number_utility/persian_number_utility.dart';
|
||||
|
||||
class NewsOverview extends StatelessWidget {
|
||||
|
|
@ -46,39 +47,57 @@ class NewsOverview extends StatelessWidget {
|
|||
children: [
|
||||
SkeletonImage(
|
||||
imageUrl: news.image,
|
||||
width: 64,
|
||||
height: 64,
|
||||
width: 100,
|
||||
height: 100,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 64,
|
||||
child: DidvanText(
|
||||
news.title,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DidvanText(
|
||||
news.title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge
|
||||
?.copyWith(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DidvanText(
|
||||
news.description,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DidvanText(
|
||||
news.description,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const DidvanDivider(verticalPadding: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
DidvanText(
|
||||
news.reference!,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
SvgPicture.asset('lib/assets/icons/calendar.svg'),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
DidvanText(
|
||||
' - ${DateTime.parse(news.createdAt).toPersianDateStr()}',
|
||||
DateTime.parse(news.createdAt).toPersianDateStr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
SvgPicture.asset('lib/assets/icons/global.svg'),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
DidvanText(
|
||||
news.reference!,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
|
|
@ -94,6 +113,7 @@ class NewsOverview extends StatelessWidget {
|
|||
onLikedChanged(news.id, value, false),
|
||||
askForConfirmation: hasUnmarkConfirmation,
|
||||
likes: news.likes,
|
||||
unlikedColor: const Color.fromARGB(255, 102, 102, 102),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4.0,
|
||||
|
|
@ -101,11 +121,15 @@ class NewsOverview extends StatelessWidget {
|
|||
BookmarkButton(
|
||||
itemId: news.id,
|
||||
type: 'news',
|
||||
gestureSize: 32,
|
||||
gestureSize: 22,
|
||||
value: news.marked,
|
||||
onMarkChanged: (value) =>
|
||||
onMarkChanged(news.id, value, false),
|
||||
askForConfirmation: hasUnmarkConfirmation,
|
||||
svgIconOn: 'lib/assets/icons/bookmark_fill.svg',
|
||||
svgIconOff: 'lib/assets/icons/archive-tick.svg',
|
||||
color: const Color.fromARGB(255, 102, 102, 102),
|
||||
unbookmarkedColor: const Color.fromARGB(255, 102, 102, 102),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,12 +21,13 @@ class TextDivider extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
padding:
|
||||
padding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Divider(
|
||||
color: lineColor ?? Theme.of(context).dividerColor,
|
||||
color: const Color.fromARGB(255, 184, 184, 184),
|
||||
thickness: lineThickness ?? 1.0,
|
||||
),
|
||||
),
|
||||
|
|
@ -34,15 +35,16 @@ class TextDivider extends StatelessWidget {
|
|||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
text,
|
||||
style: textStyle ?? Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: textColor ?? Theme.of(context).colorScheme.onSurface,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: textStyle ??
|
||||
Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: const Color.fromARGB(255, 0, 53, 70),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Divider(
|
||||
color: lineColor ?? Theme.of(context).dividerColor,
|
||||
color: const Color.fromARGB(255, 184, 184, 184),
|
||||
thickness: lineThickness ?? 1.0,
|
||||
),
|
||||
),
|
||||
|
|
@ -50,4 +52,4 @@ class TextDivider extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/app_links_linux-1.0.3/
|
||||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/device_info_plus-11.5.0/
|
||||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/file_picker-8.3.7/
|
||||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/file_selector_linux-0.9.3+2/
|
||||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_local_notifications_linux-6.0.0/
|
||||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.2/
|
||||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/gtk-2.1.0/
|
||||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_linux-0.2.1+2/
|
||||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/package_info_plus-8.3.0/
|
||||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.2.1/
|
||||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/record_linux-0.7.2/
|
||||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/sentry_flutter-8.14.2/
|
||||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_linux-3.2.1/
|
||||
|
|
@ -1 +0,0 @@
|
|||
C:/Users/UI-UX/AppData/Local/Pub/Cache/hosted/pub.dev/wakelock_plus-1.3.1/
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// This is a generated file; do not edit or check into version control.
|
||||
FLUTTER_ROOT=C:\Users\UI-UX\AppData\Local\flutter
|
||||
FLUTTER_APPLICATION_PATH=C:\Users\UI-UX\Desktop\projects\didvan-app
|
||||
FLUTTER_ROOT=C:\flutter
|
||||
FLUTTER_APPLICATION_PATH=C:\Flutter Projects\didvan-app\didvan-app
|
||||
COCOAPODS_PARALLEL_CODE_SIGN=true
|
||||
FLUTTER_BUILD_DIR=build
|
||||
FLUTTER_BUILD_NAME=4.0.1
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/sh
|
||||
# This is a generated file; do not edit or check into version control.
|
||||
export "FLUTTER_ROOT=C:\Users\UI-UX\AppData\Local\flutter"
|
||||
export "FLUTTER_APPLICATION_PATH=C:\Users\UI-UX\Desktop\projects\didvan-app"
|
||||
export "FLUTTER_ROOT=C:\flutter"
|
||||
export "FLUTTER_APPLICATION_PATH=C:\Flutter Projects\didvan-app\didvan-app"
|
||||
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
||||
export "FLUTTER_BUILD_DIR=build"
|
||||
export "FLUTTER_BUILD_NAME=4.0.1"
|
||||
|
|
|
|||
34
pubspec.lock
|
|
@ -277,10 +277,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -857,10 +857,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
version: "0.20.2"
|
||||
js:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -897,26 +897,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.8"
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1145,10 +1145,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: persian_datetime_picker
|
||||
sha256: "7ccbfd3a68dc89d405550f624e9fa590c914fed2aa2d48973c4f4400baab2e06"
|
||||
sha256: "6a5ae6b9f717a6619ae29e65e4c8074285865a88d339dd05c91b9a5b6f8f47d7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "3.2.0"
|
||||
persian_number_utility:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1462,10 +1462,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
version: "0.7.6"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1614,10 +1614,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.0"
|
||||
video_player:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1763,5 +1763,5 @@ packages:
|
|||
source: hosted
|
||||
version: "6.5.0"
|
||||
sdks:
|
||||
dart: ">=3.7.0 <4.0.0"
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.29.0"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||
version: 4.0.1+6000
|
||||
|
||||
environment:
|
||||
sdk: ">=2.19.0 <3.0.0"
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
|
|
@ -35,7 +35,7 @@ dependencies:
|
|||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
intl: ^0.19.0
|
||||
intl: 0.20.2
|
||||
animated_toggle_switch: ^0.8.2
|
||||
toggle_switch: ^2.3.0
|
||||
provider: ^6.0.1
|
||||
|
|
@ -104,7 +104,7 @@ dependencies:
|
|||
flutter_downloader: ^1.11.8
|
||||
# win32: ^5.8.0
|
||||
sentry_flutter: ^8.12.0
|
||||
persian_datetime_picker: ^3.1.0
|
||||
persian_datetime_picker: ^3.2.0
|
||||
just_audio_web: ^0.4.13
|
||||
image_cropper: ^9.0.0
|
||||
package_info_plus: ^8.3.0
|
||||
|
|
@ -118,6 +118,7 @@ dependencies:
|
|||
flutter_animate: ^4.5.2
|
||||
# image_gallery_saver: ^2.0.3
|
||||
# fading_edge_scrollview: ^4.1.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
|
|
|||