diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..27a6177 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,139 @@ +# AGENTS.md + +本文档为 Codex (Codex.ai/code) 在此代码库中工作时提供指导。 + +## 项目概览 + +这是一个名为 "Aslan" 的 Flutter 应用程序(项目 `atu`),是一个具有实时音频/视频、聊天、礼物和家族功能的社交语音聊天应用。它采用清晰架构分层:core、data、domain、presentation。 + +**主要依赖**: +- **实时通信**:Agora RTC (`agora_rtc_engine`)、腾讯云聊天 (`tencent_cloud_chat_sdk`) +- **状态管理**:Provider (`provider`) +- **路由**:Fluro (`fluro`) +- **网络请求**:Dio (`dio`) 带有自定义拦截器 +- **本地包**:修改版本的 `webview_flutter`、`tancent_vap`、`on_audio_query`、`flutter_foreground_task` 包含在 `local_packages/` 中。在更新 pub 依赖前请始终检查这些目录。 + +## 常用开发任务 + +### 运行应用 +```bash +# 运行应用 +flutter run +``` + +### 构建 +- **Android**:`flutter build apk` 或 `flutter build appbundle` +- **iOS**:`flutter build ios` + +### 测试 +- 运行单元/部件测试:`flutter test` +- 单个测试文件:`flutter test test/widget_test.dart` + +### 代码检查和分析 +- 静态分析:`flutter analyze` +- 代码格式化:`flutter format .` + +### 管理依赖 +- 获取包:`flutter pub get` +- 升级包:`flutter pub upgrade` +- 检查过时包:`flutter pub outdated` + +### iOS 特定设置 +- 更新 Flutter 插件后在 `ios/` 中运行 `pod install`。 +- Podfile 包含自定义权限标志(相机、麦克风、相册、通知已启用;其他已禁用)。按需调整。 + +## 架构 + +### 层级结构 +- **`lib/core/`**:常量、工具、共享组件、路由。 + - `constants/`:应用全局配置 (`GlobalConfig`)、颜色、屏幕尺寸。 + - `routes/`:Fluro 路由设置 (`Routes.dart`, `router_init.dart`)。 + - `compontent/`:可复用的 UI 组件(自定义应用栏、图片、按钮、对话框)。 + - `utils/`:工具类(设备 ID、键盘、深度链接)。 +- **`lib/domain/`**:业务逻辑。 + - `entities/`:数据模型。 + - `repositories/`:仓库接口。 + - `use_cases/`:业务逻辑类(文本格式化器、标签选择器等)。 +- **`lib/data/`**:数据层实现。 + - `datasources/remote/net/`:HTTP 客户端 (`http.dart`, `api.dart`),带有自定义拦截器用于头部、超时、日志。 + - `datasources/local/`:本地存储 (`StorageManager`)、用户管理 (`UserManager`)、前台服务助手。 + - `datasources/repositories/`:仓库实现。 + - `models/`:数据传输对象和枚举。 +- **`lib/presentation/`**:UI 层。 + - `pages/`:基于功能页面的目录(登录、房间、聊天、家族等)。 + - `providers/`:状态管理的 ChangeNotifier 提供者(认证、用户、房间、礼物等)。 + +### 状态管理 +- 使用 `provider` 包和 `ChangeNotifier`。 +- 提供者在 `main.dart` (`MyAppWithProviders`) 中注册。部分提供者为懒加载。 +- 关键提供者:`AuthProvider`、`UserProvider`、`RoomProvider`、`RtcProvider`、`RtmProvider`、`GiftProvider`、`LocaleProvider`、`ThemeProvider`。 + +### 路由 +- 使用 `fluro` 进行导航。 +- 路由定义在 `lib/core/routes/Routes.dart` 和各功能的模块化 `IRouterProvider` 实现 (`*_route.dart`)。 +- 导航工具:`fluro_navigator.dart` 中的 `NavigatorUtils`。 + +### 网络层 +- 基础 URL 配置在 `GlobalConfig.apiHost`。 +- 自定义 `Http` 类继承 `BaseHttp`,带有用于头部、日志、超时的拦截器。 +- 请求头包含设备信息、语言、令牌。 +- 响应包装在 `ResponseData` 中,包含 `status`、`errorCode`、`body` 字段。 + +### 配置 +- **配置架构**: 使用 `AppConfig` 系统管理应用配置 +- **全局配置**: `lib/core/constants/global_config.dart` 从 `AppConfig.current` 获取配置值 +- **配置内容**: API主机、腾讯IM App ID、Agora RTC App ID、功能开关、审核模式标志等 +- **业务逻辑差异化**: 使用策略模式(`BusinessLogicStrategy`)实现页面差异化 + - `BaseBusinessLogicStrategy`: 原始应用默认实现 + - `Variant1BusinessLogicStrategy`: 马甲包差异化实现 + - 页面通过 `GlobalConfig.businessLogicStrategy` 访问策略方法 + - 已差异化页面: 首页、登录页、家族页、探索页、个人主页、管理编辑和搜索页 + +### UI 适配 +- 使用 `flutter_screenutil` 进行响应式设计。 +- 设计尺寸为 375×667(定义在 `lib/core/constants/screen.dart`)。 +- 使用 `ScreenUtil().setWidth()`、`ScreenUtil().setHeight()`、`ScreenUtil().setSp()` 处理尺寸和字体大小。 + +### 本地化 +- `assets/l10n/` 中的 JSON 文件支持英语、中文、阿拉伯语、土耳其语、孟加拉语。 +- `ATAppLocalizations` 类加载翻译;使用 `ATAppLocalizations.of(context)!.keyName`。 + +### 资源 +- 图片按功能组织在 `images/` 中(启动页、登录、房间、个人等)。 +- 字体在 `fonts/` 中(自定义字体 `MyCustomFont`)。 + +## 重要说明 + +### 本地包 +项目在 `local_packages/` 中包含多个插件的修改版本: +- `webview_flutter-4.13.0` +- `tancent_vap-1.0.0+1`(腾讯视频动画播放器) +- `on_audio_query-2.9.0` +- `flutter_foreground_task-9.1.0` + +更新依赖时,请检查这些本地包的更改是否需要保留。除非有意替换,否则避免用 pub.dev 版本覆盖它们。 + +### iOS 权限 +`ios/Podfile` 设置预处理器标志来禁用未使用的权限(日历、联系人、提醒事项等),仅启用相机、麦克风、相册、通知。如需额外权限,请修改 `GCC_PREPROCESSOR_DEFINITIONS` 块。 + +### 审核模式 +`GlobalConfig.isReview` 控制应用是否处于审核模式(例如用于 App Store 审核)。这可能会影响功能可用性。 + +### 实时功能 +- Agora RTC 用于语音/视频房间。 +- 腾讯 IM 用于聊天消息。 +- 确保 `GlobalConfig` 中设置了正确的 App ID。 + + +### 错误报告 +- 使用 Firebase Crashlytics 进行崩溃报告。 +- 未捕获的错误通过 `runZonedGuarded` 和 `FlutterError.onError` 捕获。 +- 网络错误通过日志记录并通过 toast 显示。 + + +## 故障排除 + +- **Pod install 错误**:检查 `ios/Podfile` 语法;确保 Flutter 插件兼容。 +- **本地包错误**:验证 `pubspec.yaml` 中的路径指向正确的本地包目录。 +- **网络请求失败**:确认 `GlobalConfig.apiHost` 可达;检查 `ApiInterceptor` 中的头部。 +- **状态未更新**:验证提供者是否在需要时不是懒加载状态;检查 `notifyListeners()` 调用。 \ No newline at end of file diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index a2ee6ed..088a304 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -9,6 +9,7 @@ plugins { android { namespace = "com.chat.auu" compileSdk = 36 + ndkVersion = "28.2.13676358" bundle { @@ -25,12 +26,6 @@ android { keyPassword = "abc2025" } - getByName("debug") { - storeFile = file("./atu_debug.jks") - storePassword = "abc2025" - keyAlias = "atu" - keyPassword = "abc2025" - } } compileOptions { @@ -77,7 +72,7 @@ android { } getByName("debug") { isMinifyEnabled = false - signingConfig = signingConfigs["debug"] + signingConfig = signingConfigs["release"] } } } diff --git a/android/gradle.properties b/android/gradle.properties index f018a61..475a628 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,7 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index afa1e8e..e4ef43f 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index a207f3a..1cf140f 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -21,8 +21,8 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.7.0" apply false - id("org.jetbrains.kotlin.android") version "2.1.0" apply false + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false } include(":app") diff --git a/assets/l10n/intl_ar.json b/assets/l10n/intl_ar.json index 7796f85..a8fc9c8 100644 --- a/assets/l10n/intl_ar.json +++ b/assets/l10n/intl_ar.json @@ -325,6 +325,14 @@ "inRoom": "في الغرفة", "inRocket": "في الصاروخ", "roomRocketHelpTips": "1. إرسال الهدايا في الغرفة يزيد من طاقة الصاروخ. *هدية واحدة من العملات الذهبية = نقطة طاقة صاروخية واحدة؛ الهدايا المحظوظة تزيد من طاقة الصاروخ بنسبة 4% من قيمة العملة الذهبية للهدية.\n2. بعد اكتمال شحن الطاقة، يمكنك إطلاق الصاروخ. سيتم توزيع المكافآت تلقائيًا بعد إطلاق الصاروخ.\n3. تقدم فئات الصواريخ المختلفة مكافآت مختلفة.\n4. عند إطلاق الصاروخ، يمكن لجميع المستخدمين في الغرفة المطالبة بمكافأة الصاروخ.\n5. سيتم تحديث طاقة الصاروخ في الساعة 0:00 كل يوم.", + "roomRocketRecordAction": "السجل", + "roomRocketRewardTop1": "الأول", + "roomRocketRewardSetOff": "الإطلاق", + "roomRocketRewardInRoom": "في الغرفة", + "roomRocketResetCountdown": "23 ساعة و59 دقيقة و59 ثانية حتى إعادة التعيين", + "roomRocketRecordRoom": "الغرفة", + "roomRocketRecordReward": "المكافأة", + "roomRocketRecordLast35Days": "عرض سجلات آخر 35 يومًا فقط", "searchCouponHint": "ابحث عن كوبون", "search": "بحث", "checkInSuccessful": "تسجيل الوصول ناجح", @@ -352,7 +360,7 @@ "goToUpload": "اذهب لتحميل", "rechargeAgency": "وكالة الشحن", "logout": "تسجيل الخروج", - "adminCenter": "مركز الإدارة", + "adminCenter": "مركز العمل", "explore": "استكشاف", "micManagement": "إدارة الميكروفون", "followList": "قائمة المتابعة", @@ -756,4 +764,4 @@ "systemDefault": "النظام الافتراضي", "pleaseGetOnTheMicFirst": "من فضلك استخدم الميكروفون أولاً.", "duration2": "المدة:{1}" -} \ No newline at end of file +} diff --git a/assets/l10n/intl_bn.json b/assets/l10n/intl_bn.json index 9d62970..40c0f1b 100644 --- a/assets/l10n/intl_bn.json +++ b/assets/l10n/intl_bn.json @@ -389,6 +389,14 @@ "get": "পান করুন", "inRocket": "রকেটে", "roomRocketHelpTips": "1. রুমে উপহার পাঠান রকেট শক্তি বাড়ায়। *1 সোনা কয়েন উপহার = 1 রকেট শক্তি পয়েন্ট; ভাগ্যশালী উপহার রকেট শক্তি উপহারের সোনা কয়েন মানের 4% পরিমাণে বাড়ায়।\n2. রকেট শক্তি সম্পূর্ণভাবে পূর্ণ হলে, রুম রকেট চালু করতে পারে। চালু করার পর পুরস্কার স্বয়ংক্রিয়ভাবে বিতরণ করা হবে।\n3. বিভিন্ন রকেট স্তর বিভিন্ন পুরস্কার প্রদান করে।\n4. রকেট চালু হলে, রুমের সমস্ত ব্যবহারকারী রকেট পুরস্কার দাবি করতে পারে।5. রকেট শক্তি প্রতিদিন 00:00 এ শূন্য হয়ে যায়।", + "roomRocketRecordAction": "রেকর্ড", + "roomRocketRewardTop1": "শীর্ষ 1", + "roomRocketRewardSetOff": "চালু", + "roomRocketRewardInRoom": "রুমে", + "roomRocketResetCountdown": "রিসেট হতে ২৩ ঘণ্টা, ৫৯ মিনিট ও ৫৯ সেকেন্ড বাকি", + "roomRocketRecordRoom": "রুম", + "roomRocketRecordReward": "পুরস্কার", + "roomRocketRecordLast35Days": "শুধু গত ৩৫ দিনের রেকর্ড দেখান", "couponRecord": "কুপন ব্যবহার রেকর্ড", "inRoom": "রুমে", "searchCouponHint": "কুপন সার্চ করুন", @@ -493,7 +501,7 @@ "bdCenter": "BD সেন্টার", "renewVip": "VIP পুনর্নবীকরণ করুন", "rechargeAgency": "রিচার্জ এজেন্সি", - "adminCenter": "অ্যাডমিন সেন্টার", + "adminCenter": "ওয়ার্ক সেন্টার", "goldList": "সোনা তালিকা", "followList": "ফলো তালিকা", "fansList": "ফ্যান তালিকা", @@ -765,4 +773,4 @@ "systemDefault": "সিস্টেম ডিফল্ট", "pleaseGetOnTheMicFirst": "কৃপয়া প্রথমে মাইক্রোফোনে যান।", "duration2": "সময়:{1}" -} \ No newline at end of file +} diff --git a/assets/l10n/intl_en.json b/assets/l10n/intl_en.json index f6c09c3..ce4db9b 100644 --- a/assets/l10n/intl_en.json +++ b/assets/l10n/intl_en.json @@ -343,6 +343,14 @@ "get": "Get", "inRocket": "In the rocket", "roomRocketHelpTips": "1. Sending gifts in the room increases rocket energy. *1 gold coin gift = 1 rocket energy point; lucky gifts increase rocket energy by 4% of the gift's gold coin value.\n2. Once the rocket energy is fully charged, the room can launch the rocket. Rewards will be automatically distributed after launch.\n3. Different rocket levels offer different rewards.\n4. When the rocket launches, all users in the room can claim the rocket reward.5. Rocket energy is reset at 00:00 every day.", + "roomRocketRecordAction": "Record", + "roomRocketRewardTop1": "Top1", + "roomRocketRewardSetOff": "Set off", + "roomRocketRewardInRoom": "In the room", + "roomRocketResetCountdown": "23 hours, 59 minutes, and 59 seconds until reset", + "roomRocketRecordRoom": "Room", + "roomRocketRecordReward": "Reward", + "roomRocketRecordLast35Days": "Show only records from the last 35 days", "couponRecord": "Coupon usage record", "inRoom": "In Room", "searchCouponHint": "Search Coupon", @@ -447,7 +455,7 @@ "bdCenter": "BD Center", "renewVip": "Renew VIP", "rechargeAgency": "Recharge Agency", - "adminCenter": "Admin Center", + "adminCenter": "Work Center", "goldList": "Gold List", "followList": "Follow List", "fansList": "Fans List", @@ -766,4 +774,4 @@ "systemDefault": "System Default", "pleaseGetOnTheMicFirst": "Please get on the mic first.", "duration2": "Duration:{1}" -} \ No newline at end of file +} diff --git a/assets/l10n/intl_fil.json b/assets/l10n/intl_fil.json new file mode 100644 index 0000000..f0dd1c0 --- /dev/null +++ b/assets/l10n/intl_fil.json @@ -0,0 +1,777 @@ +{ + "signInWithGoogle": "Mag-sign in gamit ang Google", + "or": "O kaya", + "signInWithYourAccount": "Mag-sign in gamit ang iyong account", + "signInWithApple": "Mag-sign in gamit ang Apple", + "loginRepresentsAgreementTo": "Ang pag-login ay kumakatawan sa kasunduan sa", + "termsofService": "Mga tuntunin ng serbisyo", + "privaceyPolicy": "Patakaran sa Privacy", + "tips": "Mga tip", + "dailyTasksTips": "Tandaan: Handa nang harapin ang mga gawain? Hanapin ang mga ito sa iyong mga paboritong kuwarto.", + "searchNoDataTips": "Ipasok ang kwarto o user lD na gusto mong hanapin.", + "youHaveNotHadVIPYet": "Wala ka pang VIP, halika at subukan ito", + "games": "Mga laro", + "vipAccelerating": "{1} Bumibilis", + "mine": "sa akin", + "luckGiftRuleTips": "Magbigay ng masuwerteng regalo at manalo ng hanggang 1000 beses sa reward na gintong barya! Ang mga gumagamit na tumatanggap ng mga masuwerteng regalo ay maaaring tamasahin ang mga sumusunod na benepisyo: \n(1) Para sa mga user na tumatanggap ng mga masuwerteng regalo: +4% ang halaga ng karanasan sa charm batay sa halaga ng regalo. 
(2) Kung ang tatanggap ay isang host: +4% na halaga ng target na suweldo ng host batay sa halaga ng regalo.", + "badge": "Badge", + "vipRippleTheme": "VIP Ripple Theme", + "glory": "kaluwalhatian", + "badgeHonor": "Badge/Glory", + "party": "Party", + "other": "Iba pa", + "gifProfileUpload": "Pag-upload ng Profile ng GIF", + "levelMedal": "Antas na Medalya", + "levelIcon": "Icon ng Antas", + "maliciousHarassment": "Malisyosong panliligalig", + "event": "Kaganapan", + "roomEdit": "Pag-edit ng Kwarto", + "cancelRoomPassword": "Sigurado ka bang gusto mong tanggalin ang password ng kwarto?", + "roomMemberFee": "Bayad sa Miyembro ng Kwarto", + "roomTheme2": "Tema ng Kwarto", + "blockedList2": "Naka-block na Listahan", + "roomPassword": "Password ng kwarto", + "numberOfMic": "Bilang ng Mic", + "pleaseEnterContent": "Mangyaring magpasok ng nilalaman", + "profilePhoto": "Larawan sa Profile", + "aboutMe": "Tungkol sa akin", + "myRoom": "kwarto ko", + "noHistoricalRecordsAvailable": "Walang magagamit na mga makasaysayang talaan.", + "inviteNewUsersToEarnCoins": "Mag-imbita ng mga bagong user na kumita ng mga barya", + "crateMyRoom": "Lumikha ng aking silid", + "vipSpecialGiftTassel": "VIP Espesyal na tassel ng regalo", + "vipEntranceEffect": "Epekto ng VIP Entrance", + "casualInteraction": "Kaswal na Pakikipag-ugnayan", + "haveGamePlayingTips": "Mayroon kang isang laro na isinasagawa. Mangyaring lumabas sa kasalukuyang laro. Sigurado ka bang gusto mong lumabas?", + "historicalTour": "Makasaysayang Paglilibot", + "gameCenter": "Game Center", + "returnToVoiceChat": "Bumalik sa voice chat?", + "exitGameMode": "Lumabas sa Game Mode", + "enterTheRoom": "Pumasok sa kwarto", + "invite": "Mag-imbita", + "inviteGoRoomTips": "Laging nandito para sa iyo, umulan man o umaraw. Bumaba at mag-hi!", + "confirmInviteThisUserToTheRoom": "Kumpirmahin na imbitahan ang user na ito(ID:{1}) sa kwarto?", + "honor": "karangalan", + "clearCacheSuccessfully": "Matagumpay na i-clear ang cache", + "sent": "Ipinadala", + "keep": "Panatilihin", + "open": "Bukas", + "deleteAccountTips2": "*Kung magbago ang isip mo, maaari kang mag-log in muli sa iyong kasalukuyang account sa loob ng pitong araw, at awtomatiko naming ire-restore ang iyong account. Kung walang pagpapanumbalik na magaganap sa loob ng pitong araw, ang account ay permanenteng tatanggalin", + "deleteAccountTips": "Mayroon kang ganap na mga karapatang pang-administratibo sa account na ito. Kung balak mong tanggalin ang account, mangyaring magkaroon ng kamalayan sa mga sumusunod na panganib na nauugnay sa operasyong ito:\n1. Kapag ang account ay matagumpay na natanggal, hindi ka na makakapag-login sa iyong kasalukuyang account. Ang pagtanggal ng account ay isang permanenteng pagkilos.\n2. Matapos matagumpay na matanggal ang account, hindi mo na mababawi ang anumang data ng account. Ang lahat ng impormasyon (kabilang ang mga silid, kaibigan), virtual na pera, mga regalo, at mga virtual na item ay permanenteng tatanggalin at hindi na maibabalik.\n3. Panahon ng paglamig: Kung hindi mo ibabalik ang account, hindi mo maa-access ang pahina ng pagbili, pahina ng pag-withdraw, o anumang iba pang mga pahina sa app.\n4. Sa panahon ng paglamig o pagkatapos matanggal ang account, ang pahina ng profile ay magsasaad na ito ay tinanggal. Upang maprotektahan ang iyong account mula sa paghahanap o pag-access ng iba, ang iyong personal na impormasyon ay aalisin mula sa mga system na nauugnay sa mga pang-araw-araw na function. Kapag ang pagtanggal ng account ay nagsasangkot ng pambansang seguridad, sibil o kriminal na paglilitis, o ang proteksyon ng mga lehitimong karapatan at interes ng mga ikatlong partido, inilalaan ng opisyal ang karapatang tanggihan ang kahilingan sa pagtanggal ng account ng user.\nKung sigurado ka na gusto mong tanggalin ang lahat ng personal na data mula sa iyong kasalukuyang account, paki-click ang \"Delete Account.\"", + "accountDeletionNotice": "Abiso sa Pagtanggal ng Account:", + "thisUserHasBeenBlacklisted": "Ang user na ito ay nai-blacklist.", + "trend": "Uso", + "like": "Parang", + "more": "Higit pa", + "discard": "Itapon", + "catchFirstComment": "Abangan ang unang komento", + "reply": "Sumagot", + "posting": "Pagpo-post", + "dynamic": "Dynamic", + "multiple": "Maramihan", + "successfullyAddedToTheDynamicBlacklist": "Matagumpay na naidagdag sa dynamic na blacklist!", + "successfullyRemovedFromTheBlacklist": "Matagumpay na naalis sa blacklist!", + "successfullyRemovedFromTheDynamicBlacklist": "Matagumpay na naalis mula sa dynamic na blacklist!", + "successfullyAddedToTheBlacklist": "Matagumpay na naidagdag sa blacklist!", + "youAreCurrentlyCPRelationshipPleaseDissolve": "Kasalukuyan kang nasa isang CP relationship.\nPaki-dissolve muna.", + "areYouSureToCancelBlacklist": "Sigurado ka bang kanselahin ang blacklist?", + "areYouSureYouWantToBlockThisUser": "Sigurado ka bang gusto mong i-block ang user na ito?", + "areYouSureYouWantToDynamicBlockThisUser": "Sigurado ka bang magdagdag ng dynamic na blacklist?", + "removeFromBlacklist": "Alisin sa blacklist", + "moveToBlacklist": "Ilipat sa blacklist", + "userBlacklist": "Blacklist ng user", + "specialEffectsManagement": "Pamamahala ng mga espesyal na epekto", + "wishingYouHappinessEveryDay": "Nais kang kaligayahan araw-araw.", + "newMessage": "Bagong mensahe", + "createRoomSuccsess": "Gumawa ng room succsess!", + "contactUs": "Kontakin Ako", + "systemAnnouncementTips1": "Bantayan laban sa pandaraya:", + "systemAnnouncementTips": "I-verify ang impormasyon sa pamamagitan lamang ng mga opisyal na channel. Huwag kailanman mag-download ng software ng third-party, magbahagi ng personal na data, o maglipat ng pera batay sa mga panlabas na kahilingan. Ang mga opisyal na ID ng kawani ay 10000, 10003, at 10086 lamang. Para sa anumang hinala, huminto at mag-ulat sa pamamagitan ng", + "systemAnnouncement": "System Announcement", + "doNotClickUnfamiliarTips": "Huwag i-click ang mga hindi pamilyar na link, dahil maaari nilang ilantad ang iyong personal na impormasyon. Huwag kailanman ibahagi ang iyong ID o mga detalye ng bank card sa sinuman.", + "atTag": "@Tag", + "sayHi2": "Say Hi", + "canSendMsgTips": "Kailangang sundan ng magkabilang panig ang isa't isa bago sila makapagpadala ng mga pribadong mensahe.", + "msgSendRedEnvelopeTips": "*Sisingilin ang 10% na bayad sa serbisyo sa mga pulang sobre, at ang mga tatanggap ay makakatanggap lamang ng 90% ng halaga ng pulang sobre. Ang antas ng kayamanan ng nagpadala ay dapat na mas mataas kaysa sa Antas 10.", + "leavFamilyTips": "Sigurado ka bang gusto mong umalis sa iyong kasalukuyang clan? Kakailanganin mong maghintay ng 1 araw para makasali muli sa isang clan.", + "leavingTheFamily": "Iniwan ang pamilya", + "familyNotifcations": "Mga Abiso ng Pamilya", + "familyNews": "Balitang Pampamilya", + "reapply": "Mag-apply muli", + "cancelRequestFamilyMsg": "Ang iyong kahilingang sumali sa pamilya 【{1}'s family】 ay sinusuri, sigurado ka bang kanselahin ang kahilingan?", + "cancelRequest": "Kanselahin ang Kahilingan", + "pending": "Nakabinbin", + "familyAnnouncement": "Anunsyo ng Pamilya", + "enterFamilyAnnouncement": "Pakilagay ang anunsyo ng pamilya.", + "disbandTheFamily": "Buwagin ang pamilya", + "editFamily": "I-edit ang Pamilya", + "supporter": "Tagasuporta", + "rechargeSuccessful": "Matagumpay na Mag-recharge", + "transactionReceived": "Natanggap ang Transaksyon", + "createFamilySuccess": "Lumikha ng tagumpay ng pamilya.", + "numberOfSign": "Bilang ng tanda: {1}", + "hostWeeklyRank": "Host Lingguhang Ranggo", + "supporterWeeklyRank": "Lingguhang Ranggo ng Tagasuporta", + "memberList": "Listahan ng Miyembro", + "treasureChest": "Kaban ng Kayamanan", + "xxfamily": "pamilya ni {1}.", + "applicationRecord": "Record ng Application", + "createFamily": "Lumikha ng Pamilya", + "familyName": "Pangalan ng pamilya", + "createAFamily": "Lumikha ng isang pamilya", + "searchFamilyIdHint": "Pakilagay ang ID ng may-ari ng pamilya", + "enterFamilyInfo": "Pakipakilala sandali ang iyong pamilya!", + "enterFamilyName": "Pakilagay ang pangalan ng iyong pamilya.", + "familyInfo": "Panimula ng pamilya", + "joinFamily": "Sumali sa isang pamilya", + "appUpdateTip": "Ang app ay may bagong bersyon ({1}), mangyaring pumunta at i-download ito?", + "ownerIncomeCoins": "Kita ng may-ari:{1} mga barya", + "game": "Laro", + "skip2": "Laktawan", + "coins4": "mga barya", + "currentVip": "Kasalukuyang VIP", + "weekStart": "Linggo-Pagsisimula", + "forMoreRewardsPleaseCheckTheTaskCenter": "Para sa higit pang mga reward, pakitingnan ang task center", + "kingQuuen": "King-Quuen", + "ramadan": "Ramadan", + "updateNow": "Update Ngayon", + "allGames": "Lahat ng Laro", + "fishClass": "Klase ng Isda", + "greedyClass": "Matakaw na Klase", + "raceSeries": "Serye ng Lahi", + "slotsClass": "Klase ng Slots", + "others": "Iba", + "hotGames": "Mainit na Laro", + "chatBox": "Chat box", + "termsOfServicePrivacyPolicyTips": "Sa pamamagitan ng pagpapatuloy sumasang-ayon ka sa Mga Tuntunin ng Serbisyo at Patakaran sa Privacy", + "and": "at", + "pleaseSelectTheTypeContent": "Mangyaring piliin ang uri ng nakakasakit na nilalaman.", + "wearHonor": "Magsuot ng karangalan", + "illegalInformation": "Ilegal na impormasyon", + "inappropriateContent": "Hindi naaangkop na nilalaman", + "personalAttack": "Personal na pag-atake", + "confirm": "Kumpirmahin", + "spam": "Spam", + "countdownMinutes": "Countdown Minutes :", + "number2": "Numero:", + "fraud": "Panloloko", + "received": "Natanggap", + "currentProgress": "Kasalukuyang pag-unlad", + "currentStage": "Kasalukuyang yugto:{1}", + "roomReward2": "Gantimpala sa Kwarto:{1}", + "roomReward": "Gantimpala sa Kwarto", + "expirationTime": "Oras ng pag-expire", + "ownerSendTheRedEnvelope": "Ipinadala ng may-ari ang Reward coins.", + "rewardCoins": "Mga reward na barya:{1} mga barya", + "lastWeekProgress": "Ang pag-unlad noong nakaraang linggo", + "redEnvelopeTips2": "*Kung ang pulang bag ay hindi na-claim sa loob ng takdang panahon, ang natitirang mga barya ay ibabalik sa user na nagpadala ng pulang bag.", + "goToRecharge": "Pumunta sa recharge", + "deleteAccount2": "Tanggalin ang Account({1}s)", + "areYouSureYouWantToDeleteYourAccount": "Sigurado ka bang gusto mong tanggalin ang iyong account?", + "insufhcientGoldsGoToRecharge": "Hindi sapat na mga ginto, mag-recharge ngayon!", + "coins2": "{1}Mga barya", + "remainingNumberTips": "Ang natitirang bilang ng magagamit:({1}/{2})", + "collectionTimeTips": "Oras ng koleksyon:{1}({2}/{3})", + "sendARedEnvelope": "Magpadala ng pulang sobre", + "sendRedPackConfirmTips": "Sigurado ka bang gusto mong ipadala ang pulang pakete?", + "redEnvelopeSendingRecords": "Mga tala sa pagpapadala ng pulang sobre:", + "redEnvelope": "Pulang Sobre", + "redEnvelopeRecTips2": "Na-claim na lahat ang mga pulang sobre.", + "redEnvelopeRecTips3": "Ang oras ng koleksyon ng pulang sobre ay nag-expire na!", + "openTheTreasureChest": "Nagpadala ng pulang bag!", + "redEnvelopeRecTips1": "Ang mga nakuhang barya ay nadeposito sa iyong wallet.", + "redEnvelopeTips1": "Mga barya:", + "roomTools": "Mga Tool sa Kwarto:", + "entertainment": "Libangan:", + "reportSucc": "Tagumpay ang ulat", + "pornography": "Pornograpiya", + "reportInputTips": "Mangyaring ilarawan ang problema nang mas detalyado hangga't maaari upang maunawaan at malutas namin ito.", + "cancel": "Kanselahin", + "join": "Sumali", + "items": "Mga bagay", + "vistors": "Mga bisita", + "fans": "Mga tagahanga", + "balanceNotEnough": "Hindi sapat na balanse ng gintong barya. Gusto mo bang mag top up?", + "skip": "Laktawan ang {1}", + "wearMedal": "Magsuot ng Medalya", + "activityHonor": "Activity Honor", + "achievementHonor": "Achievement Honor", + "youDontHaveAnyHonorYet": "Wala ka pang karangalan.", + "letGoToWatch": "Tara manood tayo!", + "launchedARocket": "naglunsad ng rocket", + "sendUserId": "Ipadala ang UserID:{1}", + "giveUpIdentity": "Isuko ang pagkakakilanlan", + "leaveRoomIdentityTips": "Sigurado ka bang gusto mong ibigay ang pagkakakilanlan ng kwarto?", + "joinMemberTips2": "Gusto mo bang kumpirmahin ang pagsali sa kwarto bilang isang miyembro?", + "sureUnfollowThisRoom": "Sigurado bang i-unfollow ang kwartong ito?", + "welcomeMessage": "Maligayang pagdating sa aming aplikasyon, {name}!", + "settings": "Mga setting", + "account": "Account", + "common": "Karaniwan", + "delete": "Tanggalin", + "copy": "Kopyahin", + "bio": "Bio", + "useCoupontips": "Sigurado ka bang gusto mong gamitin ang kupon?", + "searchUserId": "Hanapin ang user's ID", + "sendUser": "Ipadala ang User", + "hobby": "libangan", + "sendCoupontips": "Sigurado ka bang gusto mong ipadala ang kupon na ito sa user na ito?", + "youDontHaveAnyCouponsYet": "Wala ka pang mga kupon.", + "recall": "Alalahanin", + "youHaventFollowed": "Wala ka pang sinundan na kwarto", + "deleteFromMyDevice": "Tanggalin sa aking device", + "deleteOnAllDevices": "Tanggalin sa lahat ng device", + "messageHasBeenRecalled": "Ang mensaheng ito ay naalala", + "recallThisMessage": "Tandaan ang mensaheng ito?", + "language": "Wika", + "feedback": "Feedback", + "signedin": "Naka-sign in", + "receiveSucc": "Matagumpay na na-claim", + "about": "Tungkol sa", + "aboutUs": "Tungkol sa Amin", + "theme": "Tema", + "wealthLevel": "Antas ng Kayamanan", + "userLevel": "Antas ng User", + "goToUpload": "Pumunta sa pag-upload", + "logout": "Log out", + "luck": "Swerte", + "level": "Antas", + "vip": "VIP", + "vip1": "VIP1", + "vip2": "VIP2", + "vip3": "VIP3", + "vip4": "VIP4", + "vip5": "VIP5", + "vip6": "VIP6", + "themeGoToUploadTips": "1. Suriin sa loob ng 24 na oras pagkatapos na matagumpay ang pag-upload.\n2.Ang lahat ng mga barya ay ibabalik kung nabigo ang pagsusuri.", + "home": "Bahay", + "explore": "Galugarin", + "me": "Ako", + "socialPrivilege": "Pribilehiyo sa lipunan", + "information": "Impormasyon", + "myPhoto": "Aking Larawan", + "cpRequest": "Kahilingan sa CP", + "areYouSureYouWantToSpend3": "*Kung tatanggihan ng kabilang partido ang imbitasyon sa CP, ibabalik ang iyong mga barya sa iyong wallet.", + "areYouSureYouWantToSpend": "Sigurado ka bang gusto mong gumastos", + "areYouSureYouWantToSpend2": "para magpadala ng CP invitation sa user na ito?", + "cpSexTips": "Ang mga mag-asawang magkapareho ang kasarian ay hindi maaaring gawin.", + "underReview": "Sinusuri", + "doYouWantToDeleteIt": "Gusto mo bang tanggalin ito?", + "chooseFromAblum": "Pumili mula sa Ablum", + "spaceBackground": "Background ng Space", + "editProfile": "I-edit ang Profile", + "sendTheCpRequest": "Ipadala ang kahilingan sa CP", + "addCp": "Magdagdag ng CP", + "partWays": "Mga Bahaging Paraan", + "reconcile": "Magkasundo", + "separated": "Hiwalay", + "areYouSureYouWantToSpend5": "{1} ipinagtapat ang nararamdaman sa iyo; kung tatanggapin mo, magiging couple kayo.", + "areYouSureYouWantToSpend6": "Gustong makipagbalikan ni {1} sa iyo. Kung magpasya kang makipagkasundo, maibabalik ang lahat ng iyong nakaraang data.", + "reconcileInvitationTips": "*Kung tatanggihan ng kabilang partido ang imbitasyon sa CP, ibabalik ang iyong mga barya sa iyong wallet.", + "reconcileInvitation": "Reconcile Imbitasyon", + "areYouSureYouWantToSpend4": "para magpadala ng imbitasyon sa pakikipagkasundo sa user na ito?", + "partWaysTips": "*Kung pipiliin ng isang partner sa mag-asawa na maghiwalay ng landas, magkakaroon ng 7-araw na panahon ng paglamig. Sa panahong ito, maaaring piliin ng magkabilang partido na magkasundo, at ang lahat ng data ay ibabalik sa pagkakasundo. Kung walang reconciliation ang pipiliin sa pagtatapos ng 7-araw na yugto, ang data ng mag-asawa ay iki-clear.", + "areYouSureYouWantToPartWaysWithYourCP": "Sigurado ka bang gusto mong makipaghiwalay sa iyong CP?", + "timeSpentTogether": "Oras na magkasama: {1} araw", + "firstDay": "Unang araw:{1}", + "numberOfMyCPs": "Bilang ng aking mga CP:({1}/{2})", + "props": "Props", + "medal": "Medalya", + "win": "manalo", + "dice": "Dice", + "rps": "RPS", + "areYouSureToCancelDynamicBlacklist": "Sigurado ka bang kakanselahin ang dynamic na blacklist?", + "blockUserDynamic": "I-block ang User Dynamic", + "unblockUserDynamic": "I-unblock ang User Dynamic", + "operationFail": "Nabigo ang operasyon.", + "likedYourComment": "Nagustuhan ang iyong Komento.", + "likedYourDynamic": "Nagustuhan ang iyong Dynamic.", + "doYouWantToKeepTheDraft": "Gusto mo bang panatilihin ang draft?", + "cantSendDynamicTips": "Kasalukuyan kang naka-block mula sa pag-post ng Dynamics.", + "operationsAreTooFrequent": "Masyadong madalas ang mga operasyon", + "luckNumber": "Numero ng Suwerte", + "relationShip": "Relasyon", + "couple": "Mag-asawa {1}:", + "couple2": "Mag-asawa", + "reject": "Tanggihan", + "cpList": "Listahan ng CP", + "sound2": "Tunog", + "hot": "Mainit", + "selectCountry": "Pumili ng bansa", + "giftEffect": "Epekto ng Regalo", + "winFloat": "Manalo ng Float", + "giftVibration": "Lucky Gift Effects", + "accept": "Tanggapin", + "noMatchedCP": "Walang katugmang CP", + "inviteYouToBecomeBD": "Anyayahan kang maging isang BD.", + "adminInviteRechargeAgent": "Anyayahan kang maging isang Recharge agent.", + "confirmAcceptTheInvitation": "Kumpirmahin na tanggapin ang imbitasyon?", + "confirmDeclineTheInvitation": "Kumpirmahin ang pagtanggi ng imbitasyon?", + "host": "Host", + "following": "Sumusunod", + "agent": "Ahensya", + "approved": "Naaprubahan", + "agreeJoinFamilyTips": "Pinapayagan mo ba ang user na ito na sumali sa pamilya?", + "refuseJoinFamilyTips": "Dapat mo bang tumanggi na payagan ang user na sumali sa pamilya?", + "onlineUsers": "Mga Online na User({1}/{2}):", + "applyToJoin": "Mag-apply para sumali", + "supporterList": "Listahan ng Tagasuporta", + "hostList": "Listahan ng host", + "upToAdmins": "Hanggang {1} (na) Admin", + "upToMembers": "Hanggang {1} (na) Miyembro", + "levelPrivileges": "Mga pribilehiyo sa antas", + "familyLevel": "Antas ng Pamilya", + "kickOutOfFamily": "Kick out sa pamilya", + "disbandTheFamilyTips": "Sigurado ka bang gusto mong buwagin ang pamilya?", + "setAsFamilyAdmin": "Itakda bilang admin ng pamilya", + "cancelFamilyAdmin": "Kanselahin ang admin ng pamilya", + "kickFamilyUserTips": "Sigurado ka bang gusto mong paalisin ang user na ito sa pamilya?", + "familyMember2": "Miyembro ng Pamilya({1}/{2}):", + "familyMember3": "Miyembro ng Pamilya({1}):", + "familyAdmin2": "Admin ng Pamilya({1}/{2}):", + "familyOwner2": "May-ari ng Pamilya:", + "ra": "RA", + "roomAnnouncement": "Anunsyo sa silid", + "family3": "{1} s'Family", + "help": "Tulong", + "rejected": "Tinanggihan", + "boxContributeTips": "Ang pamumuhunan ay ginawa na ngayon, mangyaring huwag mamuhunan muli", + "familyHelpTips": "(1) Kung mag-iinvest ka ng 50 coins, maa-unlock ang unang treasure chest kapag namuhunan ang 5 tao. Maaari kang mag-click para makatanggap ng 50 coins.(2)Maa-unlock ang pangalawang treasure chest kapag namuhunan ang 10 tao. Maaari kang makatanggap ng 50 barya.\n(3)Maa-unlock ang pangalawang treasure chest kapag namuhunan ang 20 tao. Makakatanggap ka ng 100 coin.\n(4) Kung ang kinakailangang bilang ng mga user na mag-claim ng treasure chest ay hindi maabot sa parehong araw, ang pag-usad ng treasure chest ay ire-reset sa susunod na araw.\n(5) Ang pag-unlad ng treasure chest ay magre-reset sa susunod na araw. Ang mga user na hindi pa na-claim ang kanilang mga treasure chest sa parehong araw ay dapat na i-claim ang mga ito sa tamang oras.\n(6) Oras ng pag-reset ng treasure chest: 00:00 oras ng Saudi.", + "bd": "BD", + "coupon": "Kupon", + "search": "Maghanap", + "get": "Kunin", + "inRocket": "Sa rocket", + "roomRocketHelpTips": "1. Ang pagpapadala ng mga regalo sa silid ay nagpapataas ng enerhiya ng rocket. *1 regalong gintong barya = 1 rocket energy point; pinapataas ng mga masuwerteng regalo ang rocket energy ng 4% ng halaga ng gintong barya ng regalo.\n2. Kapag ang enerhiya ng rocket ay ganap na na-charge, maaaring ilunsad ng silid ang rocket. Awtomatikong ipapamahagi ang mga reward pagkatapos ilunsad.\n3. Ang iba't ibang antas ng rocket ay nag-aalok ng iba't ibang mga gantimpala.\n4. Kapag naglunsad ang rocket, lahat ng user sa kuwarto ay maaaring mag-claim ng rocket reward.5. Nire-reset ang rocket energy sa 00:00 araw-araw.", + "roomRocketRecordAction": "Record", + "roomRocketRewardTop1": "Top1", + "roomRocketRewardSetOff": "Set off", + "roomRocketRewardInRoom": "Nasa room", + "roomRocketResetCountdown": "23 oras, 59 minuto, at 59 segundo bago mag-reset", + "roomRocketRecordRoom": "Room", + "roomRocketRecordReward": "Reward", + "roomRocketRecordLast35Days": "Ipakita lang ang records sa nakaraang 35 araw", + "couponRecord": "Talaan ng paggamit ng kupon", + "inRoom": "Sa Kwarto", + "searchCouponHint": "Search Coupon", + "giftCounter": "counter ng regalo", + "bDLeaderInviteYouToBecomeBDLeader": "Anyayahan kang maging isang BDLeader", + "wins": "panalo", + "inviteYouToBecomeHost": "Anyayahan kang maging isang host.", + "friends": "Mga kaibigan", + "deleteConversationTips": "Sigurado ka bang gusto mong tanggalin ang kasaysayan ng chat sa user na ito?", + "propMessagePrompt": "Prop mensahe prompt", + "inputUserId": "Ilagay ang User ID", + "fromLuckyGifts": "mula sa mga masuwerteng regalo", + "receive": "Tumanggap", + "checkInSuccessful": "Matagumpay ang pag-check-in", + "sginTips": "Makakakuha ka ng reward sa unang pagkakataon na mag-log in ka bawat araw. Kung maabala mo ang iyong pag-log in, ang reward ay kakalkulahin mula sa unang araw kung kailan ka muling nag-log in.", + "vipBadge": "VIP Badge", + "vipProfileFrame": "VIP Profile Frame", + "vipProfileCard": "VIP Profile Card", + "popular": "Sikat", + "recommend": "Magrekomenda", + "follow": "Sundin", + "history": "Kasaysayan", + "hotRooms": "Mga Mainit na Kwarto", + "viewMore": "Tingnan ang higit pa", + "noData": "Walang data", + "users": "Mga gumagamit", + "rooms": "Mga silid", + "coins": "mga barya", + "unread": "Hindi pa nababasa", + "read": "Basahin", + "image": "[Larawan]", + "video": "[Video]", + "sound": "[Tunog]", + "gift2": "[Regalo]", + "clickHereToStartChatting": "Sabihin mo.....", + "receivedAMessage": "[Nakatanggap ng mensahe]", + "confirmSwitchMicModelTips": "Kinukumpirma mo ba ang switch sa seating mode?", + "number": "Numero", + "album": "Album", + "camera": "Camera", + "system": "Sistema", + "notifcation": "Pansinin", + "inviteYouToBecomeAgent": "Anyayahan kang maging isang ahensya.", + "myMusic": "Aking Musika", + "add": "Idagdag", + "pullToLoadMore": "Hilahin para mag-load pa", + "loadingFailedClickToRetry": "Nabigo ang pag-load, i-click upang subukang muli", + "releaseToLoadMore": "I-release para mag-load pa", + "haveMyLimits": "---May limitasyon ako.---", + "music": "Musika", + "free": "Libre", + "charm": "Pang-regalo na alindog", + "start": "Magsimula", + "vipUseThisThemeTips": "Ang mga user lang na nakakatugon sa antas ng VlP ang makakagamit ng temang ito.", + "stop": "Tumigil ka", + "chats": "Mga chat", + "family": "Pamilya", + "refuse": "Tanggihan", + "agree": "Sumang-ayon", + "thisFeatureIsCurrentlyUnavailable": "Kasalukuyang hindi available ang feature na ito.", + "pleaseSelectTheRecipient": "Mangyaring piliin ang tatanggap.", + "searchMemberIdHint": "Pakilagay ang ID ng miyembro", + "unclaimedRedEnvelopes": "Ang mga hindi na-claim na pulang sobre ay ibinabalik sa loob ng 24 na oras.", + "redEnvelopeNotYetClaimed": "Hindi pa na-claim ang pulang sobre.", + "redEnvelopeAmount": "Halaga ng pulang sobre: ​​{1} mga barya", + "sentARedEnvelope": "nagpadala ng pulang sobre.", + "theRedEnvelopeHasExpired": "Nag-expire na ang pulang sobre.", + "joinRequest": "Kahilingan sa Pagsali", + "welcomeToMyFamily": "Maligayang pagdating sa aking pamilya!", + "scrollToTheBottom": "Mag-scroll sa ibaba", + "gameRules": "Mga Panuntunan sa Laro:", + "charmGameRulesTips": "I-on ang dashboard para ipakita ang natanggap na gift coin ng lahat ng user sa mic,1 coin = 1 score (Lucky gift 1 coin = 0.04 score).", + "inputYourOldPassword": "Ipasok ang iyong lumang password", + "enterYourOldPassword": "Ilagay ang iyong lumang password", + "setYourPassword": "Itakda ang iyong password", + "enterYourNewPassword": "Ipasok ang iyong bagong password", + "confirmYourPassword": "Kumpirmahin ang iyong password", + "theTwoPasswordsDoNotMatch": "Hindi magkatugma ang dalawang password.", + "resetLoginPasswordtTips2": "Ang password ay dapat na 8-16 na mga character ang haba at dapat ay isang kumbinasyon ng mga titik at maliliit na letra at numero sa Ingles (hindi lamang mga numero)", + "resetLoginPassword": "I-reset ang Login Password", + "resetLoginPasswordtTips1": "Mag-log in gamit ang iyong user ID o vanity ID. Mas ligtas na mag-log in gamit ang isang Aslan account.", + "localMusic": "Lokal na Musika", + "setLoginPassword": "Itakda ang Login Password", + "confirmSwitchMicThemeTips": "Kinukumpirma mo ba ang paglipat ng istilo ng upuan?", + "pleaseUpgradeYourVipLevelFirst": "Paki-upgrade muna ang iyong VIP level.", + "micTheme": "Tema ng Mic", + "classicMic": "Klasikong {1} Mic", + "yesterday": "Kahapon {1}", + "monday": "Lunes {1}", + "tuesday": "Martes {1}", + "wednesday": "Miyerkules {1}", + "thursday": "Huwebes {1}", + "friday": "Biyernes {1}", + "saturday": "Sabado {1}", + "sunday": "Linggo {1}", + "acceptedYour": "Tinanggap ni {1} ang iyong", + "youAccepted": "Tinanggap mo ang {1}", + "openRedPackDialogTip": "Ang pulang sobre", + "micManagement": "Pamamahala ng Mic", + "vipChatBox": "VIP Chat Box", + "vipRoomCoverBorder": "Border ng VIP Room Cover", + "bdCenter": "BD Center", + "renewVip": "I-renew ang VIP", + "rechargeAgency": "Recharge Agency", + "adminCenter": "Sentro ng Trabaho", + "goldList": "Listahan ng Ginto", + "followList": "Subaybayan ang Listahan", + "fansList": "Listahan ng mga Tagahanga", + "daily": "Araw-araw", + "cp": "CP", + "female": "Babae", + "male": "Lalaki", + "identity": "Pagkakakilanlan", + "adjust": "Ayusin", + "warning": "Babala", + "screenshotTips": "Screenshot (Hanggang 3)", + "roomNotice": "Paunawa sa silid", + "roomTheme": "Tema ng kwarto", + "description": "Paglalarawan:", + "inputDesHint": "Mangyaring ilarawan ang problema nang mas detalyado hangga't maaari upang maunawaan at malutas namin ito.", + "roomProfilePicture": "Larawan sa profile ng kwarto", + "userProfilePicture": "Larawan sa profile ng user", + "userName": "User name", + "pleaseSelectTheTypeToProcess": "Mangyaring piliin ang uri upang iproseso:", + "roomEditing": "Pag-edit ng Kwarto", + "setAccount": "Itakda ang Account", + "userEditing": "Pag-edit ng User", + "enterTheUserId": "Ilagay ang userId", + "enterTheRoomId": "Ipasok ang roomId", + "deleteAccount": "Tanggalin ang Account", + "becomeAgent": "Maging ahente", + "enterNickname": "Ilagay ang Nickname", + "selectYourCountry": "Piliin ang iyong bansa", + "inviteCode": "Code ng Imbitasyon", + "magic": "Salamangka", + "list": "Listahan", + "walletBalance": "Balanse sa pitaka:", + "canOnlyBeShown": "*Maaari lang ipakita sa lahat ng user sa iyong lugar.", + "sendMessage": "Magpadala ng Mensahe", + "availableCountries": "Magagamit na mga Bansa:", + "currentBalance": "Kasalukuyang Balanse:", + "successfulTransaction": "{1} Matagumpay na Transaksyon", + "hasBeenACoinAgencyForDays": "Naging Coin Agency Sa loob ng {1} (na) Araw", + "luckGiftSpecialEffects": "Lucky gift animation effect", + "theVideoSizeCannotExceed": "Ang laki ng video ay hindi maaaring lumampas sa 50M", + "weekly": "Linggu-linggo", + "rechargeRecords": "Recharge Records", + "balance": "balanse:", + "appStore": "App Store", + "searchCountry": "Maghanap ng bansa...", + "googlePay": "Google Pay", + "biggestDiscount": "Pinakamalaking diskwento", + "officialRechargeAgent": "Opisyal na ahente ng recharge", + "customizedGiftRulesContent": "Paano makukuha ang aking na-customize na regalo:\n1. Tukuyin kung ang user ay karapat-dapat na makatanggap ng customized na regalo batay sa kanilang kasalukuyang antas ng kayamanan.\n(1) Kapag ang antas ng kayamanan ng user ay ≥35: Maaaring mag-upload ang user ng video para sa customized na regalo nang isang beses. Gagamitin namin ang kasalukuyang larawan sa profile ng user bilang larawan ng regalo at ang ibinigay na video bilang epekto ng regalo sa \"Customized.\" Magtatagal ang produksyon, at aabisuhan ka namin sa sandaling available na ito sa tindahan.\n(2) Kapag ang antas ng kayamanan ng user ay ≥45: Maaaring mag-upload ang user ng video para sa customized na regalo nang isang beses. Gagamitin namin ang kasalukuyang larawan sa profile ng user bilang larawan ng regalo at ang ibinigay na video bilang epekto ng regalo sa \"Customized.\" Magtatagal ang produksyon, at aabisuhan ka namin sa sandaling available na ito sa tindahan.\n2.Maaari kang lumahok sa mga partikular na aktibidad sa app at, pagkatapos matugunan ang mga pamantayan, makipag-ugnayan sa amin sa pamamagitan ng seksyong \"Mensahe\" → \"Makipag-ugnayan sa amin\". Magbigay ng mga screenshot ng aktibidad at ang video na gusto mong gamitin para sa customized na regalo. Gagamitin namin ang iyong kasalukuyang larawan sa profile bilang larawan ng regalo at ang ibinigay na video bilang epekto ng regalo, pagkatapos ay ilista ito sa ilalim ng \"Na-customize.\" Magtatagal ang produksyon, at aabisuhan ka namin sa sandaling available na ito sa tindahan.\nNa-customize na Panahon ng Validity ng Regalo at Paano Ito Palawigin:\nAng mga eksklusibong customized na regalo ay magiging available sa shelf para sa validity period na 30 araw. Upang bigyang-daan ang mga maimpluwensyang at nagbibigay-inspirasyong mga user na patuloy na tangkilikin ang bagong karanasang ito at ipakita ang kanilang personal na istilo, ang mga user na nagmamay-ari ng mga customized na regalo ay maaaring pahabain ang oras ng shelf ng lahat ng kanilang mga customized na regalo nang 30 araw sa pamamagitan ng muling pagsingil ng $500 sa anumang partikular na buwan.", + "customizedGiftRules": "Mga Pasadyang Panuntunan sa Regalo", + "rulesUpload": "Mga Panuntunan at Pag-upload", + "monthly": "Buwan-buwan", + "playLog": "Play Log:", + "selectACountry": "Pumili ng bansa:", + "message": "Mensahe", + "clearCache": "I-clear ang Cache", + "customized": "Customized", + "searchInputHint": "Ilagay ang account/room number", + "kickRoomTips": "Pinalayas ka na sa kwarto.", + "joinRoomTips": "sumali sa kwarto!", + "roomSetting": "Setting ng Kwarto", + "roomDetails": "Mga Detalye ng Kwarto", + "systemRoomTips": "Mangyaring panatilihin ang kagandahang-loob at paggalang. Anumang pornograpiko o bulgar na nilalaman ay mahigpit na ipinagbabawal sa Aslan. Ang anumang account na makikitang lumalabag ay permanenteng ipagbabawal. Hinihiling namin sa lahat ng mga gumagamit na sinasadyang sumunod sa mga alituntunin ng komunidad ng Aslan.", + "copiedToClipboard": "Kinopya sa clipboard", + "recharge": "Mag-recharge", + "receivedFromALuckyGift": "Natanggap mula sa isang masuwerteng regalo.", + "followedYou": "Sinundan ka", + "agentCenter": "Sentro ng Ahensya", + "areYouSureYouWantToClearLocalCache": "Sigurado ka bang gusto mong i-clear ang lokal na cache?", + "clearMessage": "I-clear ang mga mensahe sa screen", + "report": "Ulat", + "coins3": "mga barya", + "giftSpecialEffects": "Mga espesyal na epekto ng regalo", + "basicFeatures": "Mga Pangunahing Tampok", + "task": "Gawain", + "importantReminder": "Mahalagang Paalala", + "entryVehicleAnimation": "Entry vehicle animation(VIP3)", + "floatingAnimationInGlobal": "Lumulutang na animation sa global", + "entryVehicleAnimation2": "Maaaring gamitin ng mga user na may VlP3 o mas mataas na mga pribilehiyo ang function upang i-disable ang mga animation ng sasakyan.", + "dailyTasks": "Pang-araw-araw na Gawain", + "enterRoomConfirmTips": "Sigurado ka bang gusto mong pumasok sa kwarto?", + "followSucc": "Matagumpay na nasundan", + "goldListort": "Listahan ng Ginto", + "rechargeList": "Listahan ng Recharge", + "edit": "I-edit", + "swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt": "*Tip: Mag-swipe pakaliwa sa floating screen area para mabilis itong isara.", + "enterThisVoiceChatRoom": "Pumasok sa voice chat room na ito?", + "go": "Pumunta ka", + "done": "Tapos na", + "improvementTasks": "Mga Gawain sa Pagpapabuti", + "save": "I-save", + "kickPrevention": "Pag-iwas sa sipa", + "freeChatSpeak": "Libreng Chat at Magsalita", + "vipExclusiveVehicles": "VIP Eksklusibong Sasakyan", + "nickName": "Palayaw", + "buyVip": "Bumili ng VIP", + "gender": "Kasarian", + "unFollow": "I-unfollow", + "days": "Mga araw", + "permanent": "Permanente", + "yourVipWillExpire": "Mag-e-expire ang iyong VIP sa {1}", + "cantBuyVip": "Hindi maipagpatuloy ang pagbili", + "country": "Bansa", + "birthday": "Birthday", + "man": "Lalaki", + "woman": "Babae", + "apple": "Apple", + "google": "Google", + "idIcon": "Icon ng ID", + "inviteToBecomeAHost": "Mag-imbita upang maging isang host", + "currentLevelPrivilegesAndCostumes": "Kasalukuyang antas ng mga pribilehiyo at kasuotan: {1}", + "uploadGifAvatar": "Mag-upload ng GlF avatar(VIP2)", + "uploadProfilePicture": "Mag-upload ng Larawan sa Profile", + "permissionSettings": "Mga setting ng pahintulot", + "vipMicSoundWave": "VIP Mic Sound Wave", + "startVoiceParty": "Magsimula ng voice party!", + "enterRoomTips": "{1} Pumasok sa kwarto", + "roomName": "Pangalan ng Kwarto", + "roomMember": "Miyembro ng Kwarto", + "pleaseSelectYourCountry": "Mangyaring piliin ang iyong bansa.", + "pleaseSelectYourGender": "Mangyaring piliin ang iyong kasarian.", + "pleaseEnterNickname": "Mangyaring maglagay ng palayaw.", + "countryRegion": "Bansa at Rehiyon", + "dateOfBirth": "Petsa ng kapanganakan", + "mute": "I-mute", + "exit": "Lumabas", + "mysteriousInvisibility": "Mahiwagang invisibility", + "antiBlock": "Anti-Block", + "createFamilyForFree": "Lumikha ng Pamilya nang Libre", + "privateChat": "Pribadong Chat", + "vipBirthdayGift": "VIP Birthday Gift", + "storeDiscount": "I-off ang Diskwento sa Tindahan {1}.", + "membershipFreeChatSpeak": "Chat at Speak na walang membership", + "priorityRoomSorting": "Pag-uuri ng Priyoridad na Kwarto", + "userColoredID": "User Colored ID", + "vipEmoticon": "VIP Emoticon({1})", + "pleaseSelectaItem": "Mangyaring pumili ng isang item", + "areYouRureRoRecharge": "Sigurado ka bang magrecharge?", + "mInimize": "Panatilihin", + "everyone": "lahat", + "shop": "Mamili", + "roomOwner": "May-ari ng Kwarto", + "dailyTaskRewardBonus": "Bonus sa Pang-araw-araw na Gantimpala sa Gawain({1} XP)", + "userLevelXPBoost": "User Level XP Boost({1} XP)", + "pleaseUpgradeYourVipLevel": "Paki-upgrade ang iyong VIP level.", + "andAboveUsers": "{1} At Higit sa Mga User", + "privileges": "{1} Mga Pribilehiyo", + "basicPermissions": "Mga pangunahing pahintulot", + "hostCenter": "Host Center", + "allOnMicrophone": "Lahat sa mic", + "usersOnMicrophone": "Mga user sa mic", + "allInTheRoom": "Nasa kwarto lahat", + "send": "Ipadala", + "crop": "I-crop", + "goToUpgrade": "Pumunta sa pag-upgrade", + "exclusiveEmojiWillBeReleasedAfterBecoming": "Ilalabas ang eksklusibong emoji pagkatapos maging", + "preventBeingBlocked": "Pigilan ang Pag-block", + "enableRankIncognitoMode": "I-enable ang Rank Incognito Mode", + "avoidBeingKicked": "Iwasang Sipain", + "vipPrivilege": "Pribilehiyo ng VIP", + "finish": "Tapusin", + "takeTheMic": "Kunin ang mic", + "openTheMic": "Buksan ang mikropono", + "muteTheMic": "I-mute ang mikropono", + "unlockTheMic": "I-unlock ang mikropono", + "leavelTheMic": "Iwanan ang mikropono", + "lockTheMic": "I-lock ang mikropono", + "removeTheMic": "Alisin ang mikropono", + "inviteToTheMicrophone": "Mag-imbita sa mikropono", + "openUserProfleCard": "Buksan ang user profile card", + "obtain": "makuha", + "win2": "Manalo {1}", + "backTheRoom": "Kwarto sa likod", + "toConsume": "Para ubusin", + "howToUpgrade": "Paano mag-upgrade?", + "spendCoinsToGainExperiencePoints": "Gumastos ng mga barya upang makakuha ng mga puntos ng karanasan", + "higherLevelFancierAvatarFrame": "Mas mataas na antas, mas mahilig sa mga badge/avatar frame", + "medalAndAvatarFrameRewards": "Medal at avatar frame rewards", + "all": "Lahat", + "gift": "Regalo", + "chat": "Chat", + "owner": "May-ari", + "store": "Tindahan", + "admin": "Admin", + "member": "Miyembro", + "guest": "panauhin", + "submit": "Isumite", + "claim": "Claim", + "complete": "Kumpleto", + "shareTo": "Ibahagi sa", + "copyLink": "Kopyahin ang Link", + "faceBook": "Facebook", + "whatsApp": "Whatsapp", + "snapChat": "Snapchat", + "taskNamePersonalGameConsume": "Paggastos ng Laro", + "taskNameRoomNewMember": "Mga Bagong Kwarto", + "taskNameRoomOwnerSendRedPacket": "Nagpapadala ang May-ari ng Kwarto ng isang pulang pakete", + "taskNameRoomOwnerSendGiftUser": "Nagpapadala ng Regalo ang May-ari ng Kwarto", + "taskNameRoomMicUser120Min": "Mga miyembrong may 120+ minuto sa mikropono", + "taskNameRoomMicUser60Min": "Mga miyembrong may 60+ minuto sa mikropono", + "taskNameRoomMicUser30Min": "Mga miyembrong may 30+ minuto sa mic", + "taskNamePersonalSendGift": "Magpadala ng Regalo sa User", + "taskNameRoomOnlineUserCount": "Room online na mga miyembro", + "taskNameRoomOwnerInviteMic": "Mag-imbita ng miyembro sa Mic", + "taskNameRoomUserSendGiftGold": "Mga barya na iniregalo ng mga miyembro", + "taskNameRoomOwnerSendGiftGold": "Mga barya na niregalo ng may-ari ng kuwarto", + "taskNamePersonalMagicGiftGold": "Napanalunan ang mga Coins sa pamamagitan ng Pagpapadala ng Mga Magic Gift", + "taskNamePersonalLuckyGiftGold": "Napanalunan ang mga Coins sa pamamagitan ng Pagpapadala ng Mga Lucky Gift", + "taskNameRoomOwnerMicTime": "May-ari ng Kuwarto ay naglalagay ng mikropono sa silid", + "taskNamePersonalActiveInRoom": "Maging Aktibo sa mga Kwarto ng Iba", + "taskNamePersonalMicInRoom": "Sige na Mic", + "dailyCoinBonanzaRulesDetail": "1. Ang Pang-araw-araw na Mga Personal na Gawain at Pang-araw-araw na Mga Gawain sa May-ari ng Kwarto ay maaaring kumpletuhin nang isang beses bawat user bawat araw. Na-reset ang mga gawain sa 00:00:00 oras ng Saudi.\n2. Ang mga pang-araw-araw na personal na gawain ay maaari lamang kumpletuhin sa mga silid ng iba; ang mga gawain ng may-ari ng silid ay maaari lamang makumpleto sa iyong sariling silid.\n3. Ang mga gawain sa pagbibigay ng regalo ay binibilang lamang ang mga regalong ipinadala sa mga silid, hindi ang mga regalong ipinadala sa mga feed.\n4. Kung maraming account ang ginawa gamit ang parehong device o parehong SIM card sa anumang paraan, isang beses lang ma-claim ang mga reward para sa lahat ng gawain.", + "dailyCoinBonanzaRules": "Araw-araw na Coin Bonanza Rules", + "roomOwnerTasks": "Mga Gawain sa May-ari ng Kwarto", + "personalTasks": "Mga Personal na Gawain", + "noPromptsToday": "Walang mga prompt ngayon.", + "getPaidToRefer": "Mabayaran para Mag-refer", + "membershipFee": "Bayad sa Membership", + "membershipFeeTips1": "Mangyaring itakda ang membership fee para sa iyong kuwarto. Maaaring sumali ang mga user sa iyong kwarto sa pamamagitan ng pagbabayad ng bayad.", + "membershipFeeTips2": "Ang mga gintong kinakailangan para sa user upang maging miyembro ng silid. Ang may-ari ng kuwarto ay makakakuha ng 50% ng mga ginto.", + "freePrice": "Bayad:0-10000", + "touristsSendText": "Nagpadala ng text ang turista", + "touristsTakeToTheMic": "Dinala ng mga turista ang mikropono", + "theMembershipFee": "Ang membership fee", + "theModificationsMade": "Ang mga pagbabagong ginawa sa oras na ito ay hindi mase-save pagkatapos lumabas", + "viewFrame": "Tingnan ang Frame", + "enterRoomName": "Ilagay ang pangalan ng kwarto", + "headdress": "Mga frame", + "mountains": "Mga sasakyan", + "purchaseIsSuccessful": "Matagumpay ang pagbili", + "buy": "Bumili", + "followed": "Sinundan", + "follow2": "Sundan:{1}", + "fans2": "Mga Tagahanga:{1}", + "vistors2": "Mga Bisita:{1}", + "personal2": "Personal:", + "family2": "Pamilya:", + "conntinue": "Magpatuloy", + "confirmBuyTips": "Sigurado ka bang gusto mong bumili?", + "purchase": "Bumili", + "setRoomPassword": "Itakda ang password ng kwarto", + "inputRoomPassword": "Ipasok ang password ng kwarto", + "enter": "Pumasok", + "createDynamicSuccess": "Matagumpay na nagawa ang dynamic", + "deleteDynamicTips": "Sigurado ka bang gusto mong tanggalin ang dynamic na ito?", + "deleteCommentTips": "Sigurado ka bang gusto mong tanggalin ang komentong ito?", + "deleteSuccessful": "Matagumpay ang pagtanggal!", + "itemsLeft": "Naiwan ang mga item", + "password": "Password", + "replySucc": "Matagumpay ang tugon", + "comment": "Magkomento", + "showMore": "Magpakita ng higit pa", + "showLess": "Magpakita ng mas kaunti", + "enterPassword": "Ipasok ang Password", + "enterAccount": "Ipasok ang Account", + "logIn": "Mag-log In", + "saySomething": "Sabihin mo...", + "sayHi": "Say Hi..", + "pleaseChatFfriendly": "Mangyaring makipag-chat friendly", + "unLockTheRoom": "I-unlock Ang Kwarto", + "operationSuccessful": "Naging matagumpay ang operasyon.", + "adminByHomeowner": "ay itinakda bilang administrator ng may-ari ng bahay.", + "memberByHomeowner": "ay itinakda bilang mga miyembro ng may-ari ng bahay.", + "touristByHomeowner": "ay itinakda bilang turista ng may-ari ng bahay.", + "becomeHost": "Mag-apply Upang Maging Isang Host", + "superFans": "Mga sobrang tagahanga:", + "setUpAnIdentity": "Mag-set up ng pagkakakilanlan", + "kickedOutOfRoom": "Pinalayas ng kwarto", + "playGiftMusicAndDynamicMusic": "Magpatugtog ng regalong musika at dynamic na musika", + "knapsack": "Knapsack", + "bdLeader": "Pinuno ng BD", + "picture": "Larawan", + "theImageSizeCannotExceed": "Nabigo ang pag-upload: Dapat ay mas mababa sa 2MB ang file.", + "activity": "Aktibidad", + "alreadyAnAdministrator": "Isa nang administrator", + "alreadyAnMember": "miyembro na", + "alreadyAnTourist": "Isa nang turista", + "touristsCannotSendMessages": "Ang mga turista ay hindi maaaring magpadala ng mga mensahe", + "touristsAreNotAllowedToGoOnTheMic": "Ang mga turista ay hindi pinapayagang pumunta sa mikropono", + "lockTheRoom": "I-lock Ang Kwarto", + "special": "Espesyal", + "visitorList": "Listahan ng Bisita", + "successfulWear": "Matagumpay na pagsusuot", + "confirmUnUseTips": "Kinukumpirma mo bang tanggalin ito?", + "custom": "Custom", + "myItems": "Aking Mga Item", + "use": "Gamitin", + "unUse": "Walang gamit", + "renewal": "Pag-renew", + "wallet": "Wallet", + "profile": "Profile", + "giftwall": "Giftwall", + "announcement": "Anunsyo", + "blockedList": "Naka-block na listahan", + "country2": "Bansa:", + "sendTo": "Ipadala sa", + "medals": "Mga medalya", + "activityMedal": "Medalya ng Aktibidad", + "achievementMedal": "Medalya ng Achievement", + "credits": "Mga kredito: {1}", + "successfullyUnloaded": "Matagumpay na na-unload", + "expired": "Nag-expire na", + "day": "Araw", + "inUse": "Ginagamit", + "confirmUseTips": "Gusto mo bang kumpirmahin ang paggamit nito?", + "pleaseUploadUserAvatar": "Mangyaring mag-upload ng avatar.", + "joinMemberTips": "Kung ikaw ay isang bisita sa silid, hindi mo maaaring kunin ang mikropono.", + "giftGivingSuccessful": "Matagumpay ang pagbibigay ng regalo.", + "theAccountPasswordCannotBeEmpty": "Hindi maaaring walang laman ang account o password.", + "invitesYouToTheMicrophone": "Iniimbitahan ka ni {1} sa mikropono", + "english": "Ingles", + "chinese": "Intsik", + "arabic": "Arabic", + "darkMode": "Dark Mode", + "lightMode": "Light Mode", + "systemDefault": "System Default", + "pleaseGetOnTheMicFirst": "Mangyaring sumakay muna sa mic.", + "duration2": "Tagal:{1}" +} diff --git a/assets/l10n/intl_hi.json b/assets/l10n/intl_hi.json new file mode 100644 index 0000000..bc7e266 --- /dev/null +++ b/assets/l10n/intl_hi.json @@ -0,0 +1,777 @@ +{ + "signInWithGoogle": "Google से साइन इन करें", + "or": "या", + "signInWithYourAccount": "अपने खाते से साइन इन करें", + "signInWithApple": "Apple के साथ साइन इन करें", + "loginRepresentsAgreementTo": "लॉगिन सहमति का प्रतिनिधित्व करता है", + "termsofService": "सेवा की शर्तें", + "privaceyPolicy": "गोपनीयता नीति", + "tips": "सुझावों", + "dailyTasksTips": "नोट:कार्यों से निपटने के लिए तैयार हैं? उन्हें अपने पसंदीदा कमरों में खोजें।", + "searchNoDataTips": "वह कमरा या उपयोगकर्ता आईडी दर्ज करें जिसे आप खोजना चाहते हैं।", + "youHaveNotHadVIPYet": "आपने अभी तक वीआईपी नहीं लिया है, आएं और इसे आज़माएं", + "games": "खेल", + "vipAccelerating": "{1} त्वरित करना", + "mine": "मेरा", + "luckGiftRuleTips": "एक भाग्यशाली उपहार दें और 1000 गुना तक सोने का सिक्का इनाम जीतें! भाग्यशाली उपहार प्राप्त करने वाले उपयोगकर्ता निम्नलिखित लाभों का आनंद ले सकते हैं: \n(1) भाग्यशाली उपहार प्राप्त करने वाले उपयोगकर्ताओं के लिए: उपहार के मूल्य के आधार पर +4% आकर्षण अनुभव मूल्य। 
(2) यदि प्राप्तकर्ता एक मेज़बान है: उपहार के मूल्य के आधार पर +4% मेज़बान वेतन लक्ष्य मूल्य।", + "badge": "बिल्ला", + "vipRippleTheme": "वीआईपी रिपल थीम", + "glory": "वैभव", + "badgeHonor": "बिल्ला/महिमा", + "party": "दल", + "other": "अन्य", + "gifProfileUpload": "GIF प्रोफ़ाइल अपलोड करें", + "levelMedal": "लेवल मेडल", + "levelIcon": "स्तर चिह्न", + "maliciousHarassment": "दुर्भावनापूर्ण उत्पीड़न", + "event": "आयोजन", + "roomEdit": "कक्ष संपादित करें", + "cancelRoomPassword": "क्या आप वाकई कमरे का पासवर्ड हटाना चाहते हैं?", + "roomMemberFee": "कक्ष सदस्य शुल्क", + "roomTheme2": "कक्ष थीम", + "blockedList2": "अवरुद्ध सूची", + "roomPassword": "कमरे का पासवर्ड", + "numberOfMic": "माइक की संख्या", + "pleaseEnterContent": "कृपया सामग्री दर्ज करें", + "profilePhoto": "खाते की फोटो", + "aboutMe": "मेरे बारे में", + "myRoom": "मेरा कमरा", + "noHistoricalRecordsAvailable": "कोई ऐतिहासिक अभिलेख उपलब्ध नहीं है.", + "inviteNewUsersToEarnCoins": "सिक्के कमाने के लिए नए उपयोगकर्ताओं को आमंत्रित करें", + "crateMyRoom": "मेरा कमरा बनाओ", + "vipSpecialGiftTassel": "वीआईपी विशेष उपहार लटकन", + "vipEntranceEffect": "वीआईपी प्रवेश प्रभाव", + "casualInteraction": "आकस्मिक बातचीत", + "haveGamePlayingTips": "आपका एक गेम चल रहा है. कृपया वर्तमान गेम से बाहर निकलें. क्या आप निश्चित हैं आपकी बाहर निकलने की इच्छा है?", + "historicalTour": "ऐतिहासिक यात्रा", + "gameCenter": "खेल केंद्र", + "returnToVoiceChat": "वॉइस चैट पर लौटें?", + "exitGameMode": "गेम मोड से बाहर निकलें", + "enterTheRoom": "कमरे में प्रवेश करें", + "invite": "आमंत्रित करना", + "inviteGoRoomTips": "चाहे बारिश हो या धूप, हमेशा आपके लिए यहाँ। अंदर आएं और नमस्ते कहें!", + "confirmInviteThisUserToTheRoom": "इस उपयोगकर्ता (आईडी:{1}) को कमरे में आमंत्रित करने की पुष्टि करें?", + "honor": "सम्मान", + "clearCacheSuccessfully": "कैश सफलतापूर्वक साफ़ करें", + "sent": "भेजा", + "keep": "रखना", + "open": "खुला", + "deleteAccountTips2": "*यदि आप अपना मन बदलते हैं, तो आप सात दिनों में अपने चालू खाते में वापस लॉग इन कर सकते हैं, और हम स्वचालित रूप से आपका खाता बहाल कर देंगे। यदि सात दिनों के भीतर कोई बहाली नहीं होती है, तो खाता स्थायी रूप से हटा दिया जाएगा", + "deleteAccountTips": "इस खाते पर आपके पास पूर्ण प्रशासनिक अधिकार हैं. यदि आप खाता हटाने का इरादा रखते हैं, तो कृपया इस ऑपरेशन से जुड़े निम्नलिखित जोखिमों से अवगत रहें:\n1. एक बार खाता सफलतापूर्वक हटा दिए जाने के बाद, आप अपने वर्तमान खाते में लॉगिन नहीं कर पाएंगे। खाता हटाना एक स्थायी कार्रवाई है.\n2. अकाउंट सफलतापूर्वक डिलीट होने के बाद आप अकाउंट का कोई भी डेटा रिकवर नहीं कर पाएंगे। सभी जानकारी (कमरे, दोस्तों सहित), आभासी मुद्रा, उपहार और आभासी वस्तुएं स्थायी रूप से हटा दी जाएंगी और उन्हें पुनर्स्थापित नहीं किया जा सकेगा।\n3. कूलिंग-ऑफ अवधि: यदि आप खाते को पुनर्स्थापित नहीं करते हैं, तो आप खरीद पृष्ठ, निकासी पृष्ठ, या ऐप पर किसी अन्य पृष्ठ तक पहुंचने में असमर्थ होंगे।\n4.कूलिंग-ऑफ अवधि के दौरान या खाता हटाए जाने के बाद, प्रोफ़ाइल पृष्ठ इंगित करेगा कि इसे हटा दिया गया है। आपके खाते को दूसरों द्वारा खोजे जाने या उस तक पहुंचने से बचाने के लिए, आपकी व्यक्तिगत जानकारी दैनिक कार्यों से संबंधित सिस्टम से हटा दी जाएगी। जब खाता हटाने में राष्ट्रीय सुरक्षा, नागरिक या आपराधिक कार्यवाही, या तीसरे पक्ष के वैध अधिकारों और हितों की सुरक्षा शामिल होती है, तो अधिकारी उपयोगकर्ता के खाता हटाने के अनुरोध को अस्वीकार करने का अधिकार सुरक्षित रखता है।\nयदि आप निश्चित हैं कि आप अपने चालू खाते से सभी व्यक्तिगत डेटा हटाना चाहते हैं, तो कृपया \"खाता हटाएं\" पर क्लिक करें।", + "accountDeletionNotice": "खाता हटाने की सूचना:", + "thisUserHasBeenBlacklisted": "इस उपयोगकर्ता को काली सूची में डाल दिया गया है.", + "trend": "रुझान", + "like": "पसंद", + "more": "अधिक", + "discard": "खारिज करना", + "catchFirstComment": "पहली टिप्पणी पकड़ें", + "reply": "जवाब", + "posting": "प्रविष्टि", + "dynamic": "गतिशील", + "multiple": "विभिन्न", + "successfullyAddedToTheDynamicBlacklist": "डायनामिक ब्लैकलिस्ट में सफलतापूर्वक जोड़ा गया!", + "successfullyRemovedFromTheBlacklist": "काली सूची से सफलतापूर्वक हटाया गया!", + "successfullyRemovedFromTheDynamicBlacklist": "डायनामिक ब्लैकलिस्ट से सफलतापूर्वक हटा दिया गया!", + "successfullyAddedToTheBlacklist": "ब्लैकलिस्ट में सफलतापूर्वक जोड़ा गया!", + "youAreCurrentlyCPRelationshipPleaseDissolve": "आप वर्तमान में CP संबंध में हैं.\nकृपया पहले इसे भंग करें.", + "areYouSureToCancelBlacklist": "क्या आप निश्चित रूप से काली सूची रद्द करना चाहते हैं?", + "areYouSureYouWantToBlockThisUser": "क्या वाकई आपको इस प्रयोगकर्ता को ब्लॉक करना है?", + "areYouSureYouWantToDynamicBlockThisUser": "क्या आप निश्चित रूप से डायनामिक ब्लैकलिस्ट जोड़ना चाहते हैं?", + "removeFromBlacklist": "काली सूची से हटाएँ", + "moveToBlacklist": "काली सूची में ले जाएँ", + "userBlacklist": "उपयोगकर्ता ब्लैकलिस्ट", + "specialEffectsManagement": "विशेष प्रभाव प्रबंधन", + "wishingYouHappinessEveryDay": "आपके लिए हर दिन खुशियों की कामना करता हूं।", + "newMessage": "नया सन्देश", + "createRoomSuccsess": "कमरे को सफल बनाएं!", + "contactUs": "मुझसे संपर्क करें", + "systemAnnouncementTips1": "धोखाधड़ी से बचें:", + "systemAnnouncementTips": "जानकारी को केवल आधिकारिक चैनलों के माध्यम से सत्यापित करें। कभी भी तृतीय-पक्ष सॉफ़्टवेयर डाउनलोड न करें, व्यक्तिगत डेटा साझा न करें, या बाहरी अनुरोधों के आधार पर धन हस्तांतरित न करें। आधिकारिक स्टाफ आईडी केवल 10000, 10003, और 10086 हैं। किसी भी संदेह के लिए, रुकें और रिपोर्ट करें", + "systemAnnouncement": "सिस्टम घोषणा", + "doNotClickUnfamiliarTips": "अपरिचित लिंक पर क्लिक न करें, क्योंकि वे आपकी व्यक्तिगत जानकारी को उजागर कर सकते हैं। कभी भी अपनी आईडी या बैंक कार्ड का विवरण किसी के साथ साझा न करें।", + "atTag": "@टैग", + "sayHi2": "नमस्ते कहे", + "canSendMsgTips": "निजी संदेश भेजने से पहले दोनों पक्षों को एक-दूसरे का अनुसरण करना होगा।", + "msgSendRedEnvelopeTips": "*लाल लिफाफे पर 10% सेवा शुल्क लिया जाएगा, और प्राप्तकर्ताओं को लाल लिफाफे के मूल्य का केवल 90% प्राप्त होगा। प्रेषक का धन स्तर स्तर 10 से अधिक होना चाहिए।", + "leavFamilyTips": "क्या आप वाकई अपना वर्तमान कबीला छोड़ना चाहते हैं? किसी कबीले में पुनः शामिल होने के लिए आपको 1 दिन प्रतीक्षा करनी होगी।", + "leavingTheFamily": "परिवार को छोड़कर", + "familyNotifcations": "पारिवारिक सूचनाएं", + "familyNews": "पारिवारिक समाचार", + "reapply": "पुन: लागू", + "cancelRequestFamilyMsg": "परिवार 【{1} के परिवार】 में शामिल होने का आपका अनुरोध समीक्षाधीन है, क्या आप वाकई अनुरोध रद्द करना चाहते हैं?", + "cancelRequest": "अनुरोध को रद्द करें", + "pending": "लंबित", + "familyAnnouncement": "पारिवारिक घोषणा", + "enterFamilyAnnouncement": "कृपया पारिवारिक घोषणा दर्ज करें।", + "disbandTheFamily": "परिवार को तोड़ दो", + "editFamily": "परिवार संपादित करें", + "supporter": "समर्थक", + "rechargeSuccessful": "रिचार्ज सफल", + "transactionReceived": "लेनदेन प्राप्त हुआ", + "createFamilySuccess": "पारिवारिक सफलता बनाएँ.", + "numberOfSign": "चिह्न की संख्या: {1}", + "hostWeeklyRank": "मेजबान साप्ताहिक रैंक", + "supporterWeeklyRank": "समर्थक साप्ताहिक रैंक", + "memberList": "सदस्य सूची", + "treasureChest": "खजाने की मेज", + "xxfamily": "{1} का परिवार", + "applicationRecord": "आवेदन रिकार्ड", + "createFamily": "परिवार बनाएँ", + "familyName": "पारिवारिक नाम", + "createAFamily": "एक परिवार बनाएं", + "searchFamilyIdHint": "कृपया परिवार के मालिक की आईडी दर्ज करें", + "enterFamilyInfo": "कृपया अपने परिवार का संक्षिप्त परिचय दें!", + "enterFamilyName": "कृपया अपना पारिवारिक नाम दर्ज करें.", + "familyInfo": "पारिवारिक परिचय", + "joinFamily": "एक परिवार में शामिल हों", + "appUpdateTip": "ऐप का नया संस्करण ({1}) है, कृपया जाकर इसे डाउनलोड करें?", + "ownerIncomeCoins": "मालिक की आय:{1} सिक्के", + "game": "खेल", + "skip2": "छोडना", + "coins4": "सिक्के", + "currentVip": "वर्तमान वीआईपी", + "weekStart": "सप्ताह शुरू", + "forMoreRewardsPleaseCheckTheTaskCenter": "अधिक पुरस्कारों के लिए कृपया कार्य केंद्र देखें", + "kingQuuen": "राजा-रानी", + "ramadan": "रमजान", + "updateNow": "अभी अद्यतन करें", + "allGames": "सभी खेल", + "fishClass": "मछली वर्ग", + "greedyClass": "लालची वर्ग", + "raceSeries": "दौड़ शृंखला", + "slotsClass": "स्लॉट क्लास", + "others": "अन्य", + "hotGames": "गर्म खेल", + "chatBox": "चैट बॉक्स", + "termsOfServicePrivacyPolicyTips": "जारी रखकर आप सेवा की शर्तों और गोपनीयता नीति से सहमत हैं", + "and": "और", + "pleaseSelectTheTypeContent": "कृपया आपत्तिजनक सामग्री का प्रकार चुनें.", + "wearHonor": "सम्मान धारण करें", + "illegalInformation": "अवैध जानकारी", + "inappropriateContent": "अनुपयुक्त सामग्री", + "personalAttack": "व्यक्तिगत हमला", + "confirm": "पुष्टि करना", + "spam": "अवांछित ईमेल", + "countdownMinutes": "उलटी गिनती मिनट:", + "number2": "संख्या:", + "fraud": "धोखा", + "received": "प्राप्त", + "currentProgress": "वर्तमान प्रगति", + "currentStage": "वर्तमान चरण:{1}", + "roomReward2": "कमरे का इनाम:{1}", + "roomReward": "कक्ष पुरस्कार", + "expirationTime": "समाप्ति समय", + "ownerSendTheRedEnvelope": "मालिक इनाम के सिक्के भेजता है।", + "rewardCoins": "इनाम के सिक्के:{1} सिक्के", + "lastWeekProgress": "पिछले सप्ताह की प्रगति", + "redEnvelopeTips2": "*यदि समय सीमा के भीतर लाल बैग का दावा नहीं किया जाता है, तो शेष सिक्के लाल बैग भेजने वाले उपयोगकर्ता को वापस कर दिए जाएंगे।", + "goToRecharge": "रिचार्ज पर जाएं", + "deleteAccount2": "खाता हटाएं({1}s)", + "areYouSureYouWantToDeleteYourAccount": "क्या आप इस खाते को हटाने के लिए सुनिश्चित हैं?", + "insufhcientGoldsGoToRecharge": "अपर्याप्त सोने, अभी रिचार्ज करें!", + "coins2": "{1}सिक्के", + "remainingNumberTips": "उपलब्ध की शेष संख्या:({1}/{2})", + "collectionTimeTips": "संग्रहण समय:{1}({2}/{3})", + "sendARedEnvelope": "लाल लिफाफा भेजें", + "sendRedPackConfirmTips": "क्या आप वाकई लाल पैकेट भेजना चाहते हैं?", + "redEnvelopeSendingRecords": "लाल लिफाफा भेजने वाले रिकॉर्ड:", + "redEnvelope": "लाल लिफाफा", + "redEnvelopeRecTips2": "सभी लाल लिफाफों पर दावा किया गया है।", + "redEnvelopeRecTips3": "लाल लिफाफा संग्रहण का समय समाप्त हो गया है!", + "openTheTreasureChest": "एक लाल बैग भेजा!", + "redEnvelopeRecTips1": "अर्जित सिक्के आपके बटुए में जमा कर दिए गए हैं।", + "redEnvelopeTips1": "सिक्के:", + "roomTools": "कक्ष उपकरण:", + "entertainment": "मनोरंजन:", + "reportSucc": "रिपोर्ट सफल", + "pornography": "कामोद्दीपक चित्र", + "reportInputTips": "कृपया समस्या का यथासंभव विस्तार से वर्णन करें ताकि हम इसे समझ सकें और इसका समाधान कर सकें।", + "cancel": "रद्द करना", + "join": "जोड़ना", + "items": "सामान", + "vistors": "आगंतुकों", + "fans": "प्रशंसक", + "balanceNotEnough": "अपर्याप्त सोने के सिक्के का संतुलन। क्या आप टॉप अप करना चाहते हैं?", + "skip": "छोड़ें {1}", + "wearMedal": "मेडल पहनें", + "activityHonor": "गतिविधि सम्मान", + "achievementHonor": "उपलब्धि सम्मान", + "youDontHaveAnyHonorYet": "आपके पास अभी तक कोई सम्मान नहीं है.", + "letGoToWatch": "चलो देखने चलते हैं!", + "launchedARocket": "एक रॉकेट लॉन्च किया", + "sendUserId": "उपयोगकर्ता आईडी भेजें:{1}", + "giveUpIdentity": "पहचान छोड़ो", + "leaveRoomIdentityTips": "क्या आप वाकई कमरे की पहचान छोड़ना चाहते हैं?", + "joinMemberTips2": "क्या आप सदस्य के रूप में कक्ष में शामिल होने की पुष्टि करना चाहते हैं?", + "sureUnfollowThisRoom": "क्या आप निश्चित रूप से इस कमरे को अनफ़ॉलो करना चाहते हैं?", + "welcomeMessage": "हमारे एप्लिकेशन, {नाम} में आपका स्वागत है!", + "settings": "सेटिंग्स", + "account": "खाता", + "common": "सामान्य", + "delete": "मिटाना", + "copy": "प्रतिलिपि", + "bio": "जैव", + "useCoupontips": "क्या आप वाकई कूपन का उपयोग करना चाहते हैं?", + "searchUserId": "उपयोगकर्ता की आईडी खोजें", + "sendUser": "उपयोगकर्ता भेजें", + "hobby": "शौक", + "sendCoupontips": "क्या आप वाकई इस उपयोगकर्ता को यह कूपन भेजना चाहते हैं?", + "youDontHaveAnyCouponsYet": "आपके पास अभी तक कोई कूपन नहीं है.", + "recall": "याद करना", + "youHaventFollowed": "आपने किसी भी कमरे का अनुसरण नहीं किया है", + "deleteFromMyDevice": "मेरे डिवाइस से हटाएँ", + "deleteOnAllDevices": "सभी डिवाइस पर हटाएँ", + "messageHasBeenRecalled": "इस संदेश को वापस ले लिया गया है", + "recallThisMessage": "यह संदेश याद है?", + "language": "भाषा", + "feedback": "प्रतिक्रिया", + "signedin": "साइन इन किया गया", + "receiveSucc": "सफलतापूर्वक दावा किया गया", + "about": "के बारे में", + "aboutUs": "हमारे बारे में", + "theme": "विषय", + "wealthLevel": "धन स्तर", + "userLevel": "उपयोगकर्ता स्तर", + "goToUpload": "अपलोड पर जाएं", + "logout": "लॉग आउट", + "luck": "भाग्य", + "level": "स्तर", + "vip": "वीआईपी", + "vip1": "VIP1", + "vip2": "VIP2", + "vip3": "VIP3", + "vip4": "VIP4", + "vip5": "VIP5", + "vip6": "वीआईपी6", + "themeGoToUploadTips": "1.अपलोड सफल होने के 24 घंटे के भीतर समीक्षा करें।\n2. समीक्षा विफल होने पर सभी सिक्के वापस कर दिए जाएंगे।", + "home": "घर", + "explore": "अन्वेषण करना", + "me": "मुझे", + "socialPrivilege": "सामाजिक विशेषाधिकार", + "information": "जानकारी", + "myPhoto": "मेरी फोटो", + "cpRequest": "सीपी अनुरोध", + "areYouSureYouWantToSpend3": "*यदि दूसरा पक्ष सीपी आमंत्रण को अस्वीकार करता है, तो आपके सिक्के आपके बटुए में वापस कर दिए जाएंगे।", + "areYouSureYouWantToSpend": "क्या आप वाकई खर्च करना चाहते हैं?", + "areYouSureYouWantToSpend2": "इस उपयोगकर्ता को सीपी आमंत्रण भेजने के लिए?", + "cpSexTips": "एक ही लिंग के जोड़े नहीं बनाए जा सकते.", + "underReview": "समीक्षा के अंतर्गत", + "doYouWantToDeleteIt": "क्या आप इसे हटाना चाहते हैं?", + "chooseFromAblum": "एब्लम में से चुनें", + "spaceBackground": "अंतरिक्ष पृष्ठभूमि", + "editProfile": "प्रोफ़ाइल संपादित करें", + "sendTheCpRequest": "सीपी अनुरोध भेजें", + "addCp": "सीपी जोड़ें", + "partWays": "भाग तरीके", + "reconcile": "सुलह करो", + "separated": "अलग किए", + "areYouSureYouWantToSpend5": "{1}तुम्हारे सामने भावना का इज़हार किया; यदि आप स्वीकार करते हैं, तो आप युगल बन जायेंगे।", + "areYouSureYouWantToSpend6": "{1} आपके साथ वापस आना चाहता है। यदि आप समाधान करने का निर्णय लेते हैं, तो आपका पिछला सारा डेटा पुनर्स्थापित कर दिया जाएगा।", + "reconcileInvitationTips": "*यदि दूसरा पक्ष सीपी आमंत्रण को अस्वीकार करता है, तो आपके सिक्के आपके बटुए में वापस कर दिए जाएंगे।", + "reconcileInvitation": "समाधान आमंत्रण", + "areYouSureYouWantToSpend4": "इस उपयोगकर्ता को समाधान आमंत्रण भेजने के लिए?", + "partWaysTips": "*यदि जोड़े में से एक साथी अलग होने का विकल्प चुनता है, तो 7 दिन की कूलिंग-ऑफ अवधि होगी। इस समय के दौरान, दोनों पक्ष सुलह करना चुन सकते हैं, और सुलह होने पर सभी डेटा बहाल कर दिया जाएगा। यदि 7-दिन की अवधि के अंत तक कोई सुलह नहीं चुनी जाती है, तो जोड़े का डेटा साफ़ कर दिया जाएगा।", + "areYouSureYouWantToPartWaysWithYourCP": "क्या आप वाकई अपने सीपी से अलग होना चाहते हैं?", + "timeSpentTogether": "एक साथ बिताया गया समय: {1} दिन", + "firstDay": "पहला दिन:{1}", + "numberOfMyCPs": "मेरे सीपी की संख्या:({1}/{2})", + "props": "रंगमंच की सामग्री", + "medal": "पदक", + "win": "जीतना", + "dice": "पासा", + "rps": "आर पी एस", + "areYouSureToCancelDynamicBlacklist": "क्या आप निश्चित रूप से डायनामिक ब्लैकलिस्ट रद्द करना चाहते हैं?", + "blockUserDynamic": "उपयोगकर्ता डायनामिक को ब्लॉक करें", + "unblockUserDynamic": "उपयोगकर्ता डायनामिक को अनब्लॉक करें", + "operationFail": "ऑपरेशन फेल हो गया.", + "likedYourComment": "आपकी टिप्पणी पसंद आयी.", + "likedYourDynamic": "आपका डायनामिक पसंद आया.", + "doYouWantToKeepTheDraft": "क्या आप ड्राफ्ट रखना चाहते हैं?", + "cantSendDynamicTips": "आपको वर्तमान में डायनेमिक्स पोस्ट करने से अवरुद्ध कर दिया गया है।", + "operationsAreTooFrequent": "ऑपरेशन बहुत बार होते हैं", + "luckNumber": "भाग्यांक", + "relationShip": "संबंध", + "couple": "युगल {1}:", + "couple2": "युगल", + "reject": "अस्वीकार करना", + "cpList": "सीपी सूची", + "sound2": "आवाज़", + "hot": "गर्म", + "selectCountry": "देश चुनें", + "giftEffect": "उपहार प्रभाव", + "winFloat": "फ्लोट जीतो", + "giftVibration": "भाग्यशाली उपहार प्रभाव", + "accept": "स्वीकार करना", + "noMatchedCP": "कोई मिलान सीपी नहीं", + "inviteYouToBecomeBD": "आपको बीडी बनने के लिए आमंत्रित करें।", + "adminInviteRechargeAgent": "आपको रिचार्ज एजेंट बनने के लिए आमंत्रित करें।", + "confirmAcceptTheInvitation": "निमंत्रण स्वीकार करने की पुष्टि करें?", + "confirmDeclineTheInvitation": "निमंत्रण की अस्वीकृति की पुष्टि करें?", + "host": "मेज़बान", + "following": "अगले", + "agent": "एजेंसी", + "approved": "अनुमत", + "agreeJoinFamilyTips": "क्या आप इस उपयोगकर्ता को परिवार में शामिल होने की अनुमति देते हैं?", + "refuseJoinFamilyTips": "क्या आपको उपयोगकर्ता को परिवार में शामिल होने से मना कर देना चाहिए?", + "onlineUsers": "ऑनलाइन उपयोगकर्ता({1}/{2}):", + "applyToJoin": "जुड़ने के लिए आवेदन करें", + "supporterList": "समर्थकों की सूची", + "hostList": "मेज़बान सूची", + "upToAdmins": "{1} एडमिन तक", + "upToMembers": "अधिकतम {1} सदस्य", + "levelPrivileges": "स्तरीय विशेषाधिकार", + "familyLevel": "पारिवारिक स्तर", + "kickOutOfFamily": "परिवार से बाहर निकालो", + "disbandTheFamilyTips": "क्या आप वाकई परिवार को ख़त्म करना चाहते हैं?", + "setAsFamilyAdmin": "पारिवारिक व्यवस्थापक के रूप में सेट करें", + "cancelFamilyAdmin": "परिवार व्यवस्थापक रद्द करें", + "kickFamilyUserTips": "क्या आप वाकई इस उपयोगकर्ता को परिवार से बाहर निकालना चाहते हैं?", + "familyMember2": "परिवार के सदस्य({1}/{2}):", + "familyMember3": "परिवार का सदस्य({1}):", + "familyAdmin2": "पारिवारिक व्यवस्थापक({1}/{2}):", + "familyOwner2": "परिवार स्वामी:", + "ra": "आरए", + "roomAnnouncement": "कमरे की घोषणा", + "family3": "{1} परिवार", + "help": "मदद", + "rejected": "अस्वीकार कर दिया", + "boxContributeTips": "आज निवेश पहले ही किया जा चुका है, कृपया दोबारा निवेश न करें", + "familyHelpTips": "(1) यदि आप 50 सिक्के निवेश करते हैं, तो पहला खजाना तब खुल जाएगा जब 5 लोग निवेश करेंगे। आप 50 सिक्के प्राप्त करने के लिए क्लिक कर सकते हैं।(2)जब 10 लोग निवेश करेंगे तो दूसरा खज़ाना खुल जाएगा। आप 50 सिक्के प्राप्त कर सकते हैं.\n(3) दूसरा खज़ाना तब खुलेगा जब 20 लोग निवेश करेंगे। आप 100 सिक्के प्राप्त कर सकते हैं.\n(4) यदि खजाना पेटी पर दावा करने के लिए उपयोगकर्ताओं की आवश्यक संख्या उसी दिन नहीं पहुंचती है, तो खजाना पेटी की प्रगति अगले दिन रीसेट कर दी जाएगी।\n(5)खजाना संदूक की प्रगति अगले दिन रीसेट हो जाएगी। जिन उपयोगकर्ताओं ने अभी तक उसी दिन अपने खजाने का दावा नहीं किया है, उन्हें समय पर दावा करना चाहिए।\n(6)खजाना संदूक रीसेट समय: 00:00 सऊदी समय।", + "bd": "बी.डी", + "coupon": "कूपन", + "search": "खोज", + "get": "पाना", + "inRocket": "रॉकेट में", + "roomRocketHelpTips": "1. कमरे में उपहार भेजने से रॉकेट ऊर्जा बढ़ती है। *1 सोने का सिक्का उपहार = 1 रॉकेट ऊर्जा बिंदु; भाग्यशाली उपहार रॉकेट ऊर्जा को उपहार के सोने के सिक्के के मूल्य से 4% तक बढ़ा देते हैं।\n2. एक बार रॉकेट ऊर्जा पूरी तरह चार्ज हो जाने पर, कक्ष रॉकेट लॉन्च कर सकता है। लॉन्च के बाद पुरस्कार स्वचालित रूप से वितरित किए जाएंगे।\n3. विभिन्न रॉकेट स्तर अलग-अलग पुरस्कार प्रदान करते हैं।\n4. जब रॉकेट लॉन्च होता है, तो कमरे में मौजूद सभी उपयोगकर्ता रॉकेट इनाम का दावा कर सकते हैं।5. रॉकेट ऊर्जा हर दिन 00:00 बजे रीसेट की जाती है।", + "roomRocketRecordAction": "रिकॉर्ड", + "roomRocketRewardTop1": "Top1", + "roomRocketRewardSetOff": "लॉन्च", + "roomRocketRewardInRoom": "कमरे में", + "roomRocketResetCountdown": "रीसेट होने में 23 घंटे, 59 मिनट और 59 सेकंड", + "roomRocketRecordRoom": "कमरा", + "roomRocketRecordReward": "इनाम", + "roomRocketRecordLast35Days": "केवल पिछले 35 दिनों के रिकॉर्ड दिखाएं", + "couponRecord": "कूपन उपयोग रिकॉर्ड", + "inRoom": "कमरे में", + "searchCouponHint": "कूपन खोजें", + "giftCounter": "उपहार काउंटर", + "bDLeaderInviteYouToBecomeBDLeader": "आपको बीडीलीडर बनने के लिए आमंत्रित करें", + "wins": "जीत", + "inviteYouToBecomeHost": "आपको मेज़बान बनने के लिए आमंत्रित करें.", + "friends": "दोस्त", + "deleteConversationTips": "क्या आप वाकई इस उपयोगकर्ता के साथ चैट इतिहास को हटाना चाहते हैं?", + "propMessagePrompt": "प्रोप संदेश शीघ्र", + "inputUserId": "उपयोगकर्ता आईडी दर्ज करें", + "fromLuckyGifts": "भाग्यशाली उपहारों से", + "receive": "प्राप्त करें", + "checkInSuccessful": "चेक-इन सफल", + "sginTips": "हर दिन पहली बार लॉग इन करने पर आपको इनाम मिलेगा। यदि आप अपना लॉगिन बाधित करते हैं, तो इनाम की गणना आपके दोबारा लॉग इन करने पर पहले दिन से की जाएगी।", + "vipBadge": "वीआईपी बैज", + "vipProfileFrame": "वीआईपी प्रोफ़ाइल फ़्रेम", + "vipProfileCard": "वीआईपी प्रोफ़ाइल कार्ड", + "popular": "लोकप्रिय", + "recommend": "अनुशंसा करना", + "follow": "अनुसरण करना", + "history": "इतिहास", + "hotRooms": "गर्म कमरे", + "viewMore": "और देखें", + "noData": "कोई डेटा नहीं", + "users": "उपयोगकर्ताओं", + "rooms": "कमरा", + "coins": "सिक्के", + "unread": "अपठित ग", + "read": "पढ़ना", + "image": "[छवि]", + "video": "[वीडियो]", + "sound": "[आवाज़]", + "gift2": "[उपहार]", + "clickHereToStartChatting": "कुछ कहो.....", + "receivedAMessage": "[एक संदेश प्राप्त हुआ]", + "confirmSwitchMicModelTips": "क्या आप सीटिंग मोड में स्विच की पुष्टि करते हैं?", + "number": "संख्या", + "album": "एल्बम", + "camera": "कैमरा", + "system": "प्रणाली", + "notifcation": "सूचना", + "inviteYouToBecomeAgent": "आपको एक एजेंसी बनने के लिए आमंत्रित करें.", + "myMusic": "मेरे संगीत", + "add": "जोड़ना", + "pullToLoadMore": "अधिक लोड करने के लिए खींचें", + "loadingFailedClickToRetry": "लोडिंग विफल, पुनः प्रयास करने के लिए क्लिक करें", + "releaseToLoadMore": "अधिक लोड करने के लिए रिलीज़ करें", + "haveMyLimits": "---मेरी अपनी सीमाएँ हैं।---", + "music": "संगीत", + "free": "मुक्त", + "charm": "उपहार आकर्षण", + "start": "शुरू", + "vipUseThisThemeTips": "केवल वीएलपी स्तर को पूरा करने वाले उपयोगकर्ता ही इस थीम का उपयोग कर सकते हैं।", + "stop": "रुकना", + "chats": "चैट", + "family": "परिवार", + "refuse": "अस्वीकार करना", + "agree": "सहमत", + "thisFeatureIsCurrentlyUnavailable": "यह विशेषता इस समय उपलब्ध नहीं है।", + "pleaseSelectTheRecipient": "कृपया प्राप्तकर्ता का चयन करें.", + "searchMemberIdHint": "कृपया सदस्य की आईडी दर्ज करें", + "unclaimedRedEnvelopes": "लावारिस लाल लिफाफे 24 घंटे में वापस कर दिए जाते हैं।", + "redEnvelopeNotYetClaimed": "लाल लिफाफे पर अभी तक दावा नहीं किया गया है।", + "redEnvelopeAmount": "लाल लिफ़ाफ़ा राशि: {1} सिक्के", + "sentARedEnvelope": "एक लाल लिफाफा भेजा.", + "theRedEnvelopeHasExpired": "लाल लिफाफा समाप्त हो गया है.", + "joinRequest": "शामिल होने का अनुरोध", + "welcomeToMyFamily": "मेरे परिवार में आपका स्वागत है!", + "scrollToTheBottom": "नीचे तक स्क्रॉल करें", + "gameRules": "खेल के नियम:", + "charmGameRulesTips": "सभी उपयोगकर्ताओं के प्राप्त उपहार सिक्के को माइक पर दिखाने के लिए डैशबोर्ड चालू करें, 1 सिक्का = 1 अंक (भाग्यशाली उपहार 1 सिक्का = 0.04 अंक)।", + "inputYourOldPassword": "अपना पुराना पासवर्ड डालें", + "enterYourOldPassword": "अपना पुराना पासवर्ड डालें", + "setYourPassword": "अपना पासवर्ड सेट करें", + "enterYourNewPassword": "अपना नया पासवर्ड दर्ज करें", + "confirmYourPassword": "अपने पासवर्ड की पुष्टि करें", + "theTwoPasswordsDoNotMatch": "दो पासवर्ड मेल नहीं खाता हैं।", + "resetLoginPasswordtTips2": "पासवर्ड 8-16 अक्षर लंबा होना चाहिए और इसमें अंग्रेजी के अपरकेस और लोअरकेस अक्षरों और संख्याओं का संयोजन होना चाहिए (सिर्फ संख्याएं नहीं)", + "resetLoginPassword": "लॉगिन पासवर्ड रीसेट करें", + "resetLoginPasswordtTips1": "अपनी यूजर आईडी या वैनिटी आईडी से लॉग इन करें। असलान खाते से लॉग इन करना अधिक सुरक्षित है।", + "localMusic": "स्थानीय संगीत", + "setLoginPassword": "लॉगिन पासवर्ड सेट करें", + "confirmSwitchMicThemeTips": "क्या आप सीट शैली के बदलाव की पुष्टि करते हैं?", + "pleaseUpgradeYourVipLevelFirst": "कृपया पहले अपना वीआईपी स्तर अपग्रेड करें।", + "micTheme": "माइक थीम", + "classicMic": "क्लासिक {1} माइक", + "yesterday": "कल {1}", + "monday": "सोमवार {1}", + "tuesday": "मंगलवार {1}", + "wednesday": "बुधवार {1}", + "thursday": "गुरुवार {1}", + "friday": "शुक्रवार {1}", + "saturday": "शनिवार {1}", + "sunday": "रविवार {1}", + "acceptedYour": "{1} ने आपका स्वीकार कर लिया", + "youAccepted": "आपने स्वीकार कर लिया है {1}", + "openRedPackDialogTip": "लाल लिफाफा", + "micManagement": "माइक प्रबंधन", + "vipChatBox": "वीआईपी चैट बॉक्स", + "vipRoomCoverBorder": "वीआईपी रूम कवर बॉर्डर", + "bdCenter": "बीडी सेंटर", + "renewVip": "वीआईपी नवीनीकृत करें", + "rechargeAgency": "रिचार्ज एजेंसी", + "adminCenter": "कार्य केंद्र", + "goldList": "सोने की सूची", + "followList": "सूची का पालन करें", + "fansList": "प्रशंसकों की सूची", + "daily": "दैनिक", + "cp": "सीपी", + "female": "महिला", + "male": "पुरुष", + "identity": "पहचान", + "adjust": "समायोजित करना", + "warning": "चेतावनी", + "screenshotTips": "स्क्रीनशॉट (3 तक)", + "roomNotice": "कक्ष सूचना", + "roomTheme": "कक्ष विषय", + "description": "विवरण:", + "inputDesHint": "कृपया समस्या का यथासंभव विस्तार से वर्णन करें ताकि हम इसे समझ सकें और इसका समाधान कर सकें।", + "roomProfilePicture": "कमरे का प्रोफ़ाइल चित्र", + "userProfilePicture": "उपयोगकर्ता प्रोफ़ाइल चित्र", + "userName": "उपयोगकर्ता नाम", + "pleaseSelectTheTypeToProcess": "कृपया संसाधित करने के लिए प्रकार का चयन करें:", + "roomEditing": "कक्ष संपादन", + "setAccount": "खाता सेट करें", + "userEditing": "उपयोगकर्ता संपादन", + "enterTheUserId": "उपयोगकर्ता आईडी दर्ज करें", + "enterTheRoomId": "रूम आईडी दर्ज करें", + "deleteAccount": "खाता हटा दो", + "becomeAgent": "एक एजेंट बनें", + "enterNickname": "उपनाम दर्ज करें", + "selectYourCountry": "अपने देश का चयन करॊ", + "inviteCode": "कोड आमंत्रित", + "magic": "जादू", + "list": "सूची", + "walletBalance": "बटुआ शेष:", + "canOnlyBeShown": "*केवल आपके क्षेत्र के सभी उपयोगकर्ताओं को दिखाया जा सकता है।", + "sendMessage": "मेसेज भेजें", + "availableCountries": "उपलब्ध देश:", + "currentBalance": "वर्तमान शेष:", + "successfulTransaction": "{1} सफल लेनदेन", + "hasBeenACoinAgencyForDays": "{1} दिनों से एक सिक्का एजेंसी रही है", + "luckGiftSpecialEffects": "भाग्यशाली उपहार एनीमेशन प्रभाव", + "theVideoSizeCannotExceed": "वीडियो का आकार 50M से अधिक नहीं हो सकता", + "weekly": "साप्ताहिक", + "rechargeRecords": "पुनर्भरण रिकार्ड", + "balance": "संतुलन:", + "appStore": "ऐप स्टोर", + "searchCountry": "देश खोजें...", + "googlePay": "गूगल पे", + "biggestDiscount": "सबसे बड़ी छूट", + "officialRechargeAgent": "आधिकारिक रिचार्ज एजेंट", + "customizedGiftRulesContent": "मेरा अनुकूलित उपहार कैसे प्राप्त करें:\n1. निर्धारित करें कि क्या उपयोगकर्ता अपने वर्तमान धन स्तर के आधार पर अनुकूलित उपहार प्राप्त करने के योग्य है।\n(1) जब उपयोगकर्ता का धन स्तर ≥35 है: उपयोगकर्ता अनुकूलित उपहार के लिए एक बार वीडियो अपलोड कर सकता है। हम उपयोगकर्ता की वर्तमान प्रोफ़ाइल तस्वीर को उपहार छवि के रूप में और प्रदान किए गए वीडियो को \"अनुकूलित\" पर उपहार प्रभाव के रूप में उपयोग करेंगे। उत्पादन में कुछ समय लगेगा, और जैसे ही यह स्टोर में उपलब्ध होगा हम आपको सूचित करेंगे।\n(2) जब उपयोगकर्ता का धन स्तर 45 हो: उपयोगकर्ता अनुकूलित उपहार के लिए एक बार वीडियो अपलोड कर सकता है। हम उपयोगकर्ता की वर्तमान प्रोफ़ाइल तस्वीर को उपहार छवि के रूप में और प्रदान किए गए वीडियो को \"अनुकूलित\" पर उपहार प्रभाव के रूप में उपयोग करेंगे। उत्पादन में कुछ समय लगेगा, और जैसे ही यह स्टोर में उपलब्ध होगा हम आपको सूचित करेंगे।\n2.आप ऐप पर विशिष्ट गतिविधियों में भाग ले सकते हैं और मानदंडों को पूरा करने के बाद, \"संदेश\" → \"हमसे संपर्क करें\" अनुभाग के माध्यम से हमसे संपर्क करें। गतिविधि और वीडियो के स्क्रीनशॉट प्रदान करें जिन्हें आप अनुकूलित उपहार के लिए उपयोग करना चाहते हैं। हम आपकी वर्तमान प्रोफ़ाइल तस्वीर को उपहार छवि के रूप में और दिए गए वीडियो को उपहार प्रभाव के रूप में उपयोग करेंगे, फिर इसे \"अनुकूलित\" के अंतर्गत सूचीबद्ध करेंगे। उत्पादन में कुछ समय लगेगा, और जैसे ही यह स्टोर में उपलब्ध होगा हम आपको सूचित करेंगे।\nअनुकूलित उपहार वैधता अवधि और इसे कैसे बढ़ाएं:\nविशेष अनुकूलित उपहार 30 दिनों की वैधता अवधि के लिए शेल्फ पर उपलब्ध होंगे। प्रभावशाली और प्रेरक उपयोगकर्ताओं को इस नए अनुभव का आनंद लेने और अपनी व्यक्तिगत शैली का प्रदर्शन करने में सक्षम बनाने के लिए, जिन उपयोगकर्ताओं के पास अनुकूलित उपहार हैं, वे किसी भी महीने में $500 का रिचार्ज करके अपने सभी अनुकूलित उपहारों के शेल्फ समय को 30 दिनों तक बढ़ा सकते हैं।", + "customizedGiftRules": "अनुकूलित उपहार नियम", + "rulesUpload": "नियम एवं अपलोड", + "monthly": "महीने के", + "playLog": "प्ले लॉग:", + "selectACountry": "कोई देश चुनें:", + "message": "संदेश", + "clearCache": "कैश को साफ़ करें", + "customized": "स्वनिर्धारित", + "searchInputHint": "खाता/कमरा नंबर दर्ज करें", + "kickRoomTips": "तुम्हें कमरे से बाहर निकाल दिया गया है.", + "joinRoomTips": "सम्मिलित कक्ष!", + "roomSetting": "कक्ष सेटिंग", + "roomDetails": "कमरे का विवरण", + "systemRoomTips": "कृपया शिष्टाचार और सम्मान बनाए रखें। असलान पर कोई भी अश्लील या अश्लील सामग्री सख्त वर्जित है। उल्लंघन में पाया गया कोई भी खाता स्थायी रूप से प्रतिबंधित कर दिया जाएगा। हम सभी उपयोगकर्ताओं से अनुरोध करते हैं कि वे सचेत रूप से असलान के सामुदायिक दिशानिर्देशों का पालन करें।", + "copiedToClipboard": "क्लिपबोर्ड पर कॉपी किया गया", + "recharge": "फिर से दाम लगाना", + "receivedFromALuckyGift": "एक भाग्यशाली उपहार से प्राप्त हुआ.", + "followedYou": "तुम्हारा पीछा", + "agentCenter": "एजेंसी केंद्र", + "areYouSureYouWantToClearLocalCache": "क्या आप वाकई स्थानीय कैश साफ़ करना चाहते हैं?", + "clearMessage": "स्क्रीन संदेश साफ़ करें", + "report": "प्रतिवेदन", + "coins3": "सिक्के", + "giftSpecialEffects": "विशेष प्रभाव उपहार दें", + "basicFeatures": "बुनियादी सुविधाएँ", + "task": "काम", + "importantReminder": "महत्वपूर्ण अनुस्मारक", + "entryVehicleAnimation": "प्रवेश वाहन एनीमेशन(VIP3)", + "floatingAnimationInGlobal": "ग्लोबल में फ़्लोटिंग एनीमेशन", + "entryVehicleAnimation2": "VlP3 या उच्चतर विशेषाधिकार वाले उपयोगकर्ता वाहन एनिमेशन को अक्षम करने के लिए फ़ंक्शन का उपयोग कर सकते हैं।", + "dailyTasks": "दैनिक कार्यों", + "enterRoomConfirmTips": "क्या आप वाकई कमरे में प्रवेश करना चाहते हैं?", + "followSucc": "सफलतापूर्वक पालन किया गया", + "goldListort": "सोने की सूची", + "rechargeList": "रिचार्ज सूची", + "edit": "संपादन करना", + "swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt": "*टिप: फ़्लोटिंग स्क्रीन क्षेत्र को तुरंत बंद करने के लिए उस पर बाईं ओर स्वाइप करें।", + "enterThisVoiceChatRoom": "इस ध्वनि चैट रूम में प्रवेश करें?", + "go": "जाना", + "done": "हो गया", + "improvementTasks": "सुधार कार्य", + "save": "बचाना", + "kickPrevention": "किक रोकथाम", + "freeChatSpeak": "मुफ़्त चैट करें और बोलें", + "vipExclusiveVehicles": "वीआईपी विशिष्ट वाहन", + "nickName": "उपनाम", + "buyVip": "वीआईपी खरीदें", + "gender": "लिंग", + "unFollow": "करें", + "days": "दिन", + "permanent": "स्थायी", + "yourVipWillExpire": "आपका वीआईपी {1} को समाप्त हो जाएगा", + "cantBuyVip": "खरीदारी जारी रखने में असमर्थ", + "country": "देश", + "birthday": "जन्मदिन", + "man": "आदमी", + "woman": "महिला", + "apple": "सेब", + "google": "गूगल", + "idIcon": "आईडी चिह्न", + "inviteToBecomeAHost": "मेज़बान बनने के लिए आमंत्रित करें", + "currentLevelPrivilegesAndCostumes": "वर्तमान स्तर के विशेषाधिकार और वेशभूषा: {1}", + "uploadGifAvatar": "GlF अवतार अपलोड करें(VIP2)", + "uploadProfilePicture": "प्रोफ़ाइल चित्र अपलोड करें", + "permissionSettings": "अनुमति सेटिंग्स", + "vipMicSoundWave": "वीआईपी माइक ध्वनि तरंग", + "startVoiceParty": "एक आवाज पार्टी शुरू करें!", + "enterRoomTips": "{1} कमरे में प्रवेश करें", + "roomName": "कमरे का नाम", + "roomMember": "कक्ष सदस्य", + "pleaseSelectYourCountry": "कृपया अपना देश चुनें.", + "pleaseSelectYourGender": "कृपया अपना लिंग चुनें.", + "pleaseEnterNickname": "कृपया एक निकनाम दर्ज करें।", + "countryRegion": "देश एवं क्षेत्र", + "dateOfBirth": "जन्मतिथि", + "mute": "आवाज़ बंद करना", + "exit": "बाहर निकलना", + "mysteriousInvisibility": "रहस्यमय अदृश्यता", + "antiBlock": "विरोधी अवरोध", + "createFamilyForFree": "मुफ़्त में परिवार बनाएं", + "privateChat": "निजी चैट", + "vipBirthdayGift": "वीआईपी जन्मदिन उपहार", + "storeDiscount": "स्टोर डिस्काउंट {1} बंद", + "membershipFreeChatSpeak": "सदस्यता-मुक्त चैट करें और बोलें", + "priorityRoomSorting": "प्राथमिकता कक्ष छँटाई", + "userColoredID": "उपयोगकर्ता रंगीन आईडी", + "vipEmoticon": "वीआईपी इमोटिकॉन({1})", + "pleaseSelectaItem": "कृपया एक आइटम चुनें", + "areYouRureRoRecharge": "क्या आप निश्चित रूप से रिचार्ज करेंगे?", + "mInimize": "रखना", + "everyone": "सब लोग", + "shop": "दुकान", + "roomOwner": "कमरे का मालिक", + "dailyTaskRewardBonus": "दैनिक कार्य पुरस्कार बोनस({1} XP)", + "userLevelXPBoost": "उपयोगकर्ता स्तर XP बूस्ट({1} XP)", + "pleaseUpgradeYourVipLevel": "कृपया अपना वीआईपी स्तर अपग्रेड करें।", + "andAboveUsers": "{1} और उससे ऊपर के उपयोगकर्ता", + "privileges": "{1} विशेषाधिकार", + "basicPermissions": "बुनियादी अनुमतियाँ", + "hostCenter": "मेज़बान केंद्र", + "allOnMicrophone": "सब माइक पर", + "usersOnMicrophone": "माइक पर यूजर्स", + "allInTheRoom": "सभी कमरे में", + "send": "भेजना", + "crop": "काटना", + "goToUpgrade": "अपग्रेड करने के लिए जाएं", + "exclusiveEmojiWillBeReleasedAfterBecoming": "एक्सक्लूसिव इमोजी बनने के बाद जारी किया जाएगा", + "preventBeingBlocked": "अवरुद्ध होने से रोकें", + "enableRankIncognitoMode": "रैंक गुप्त मोड सक्षम करें", + "avoidBeingKicked": "लात खाने से बचें", + "vipPrivilege": "वीआईपी विशेषाधिकार", + "finish": "खत्म करना", + "takeTheMic": "माइक ले लो", + "openTheMic": "माइक खोलो", + "muteTheMic": "माइक म्यूट करें", + "unlockTheMic": "माइक अनलॉक करें", + "leavelTheMic": "माइक छोड़ो", + "lockTheMic": "माइक लॉक करें", + "removeTheMic": "माइक हटाओ", + "inviteToTheMicrophone": "माइक्रोफ़ोन पर आमंत्रित करें", + "openUserProfleCard": "उपयोगकर्ता प्रोफ़ाइल कार्ड खोलें", + "obtain": "प्राप्त", + "win2": "जीतो {1}", + "backTheRoom": "पीछे का कमरा", + "toConsume": "उपभोग करना", + "howToUpgrade": "अपग्रेड कैसे करें?", + "spendCoinsToGainExperiencePoints": "अनुभव अंक प्राप्त करने के लिए सिक्के खर्च करें", + "higherLevelFancierAvatarFrame": "उच्च स्तर, शानदार बैज/अवतार फ्रेम", + "medalAndAvatarFrameRewards": "पदक और अवतार फ़्रेम पुरस्कार", + "all": "सभी", + "gift": "उपहार", + "chat": "बात करना", + "owner": "मालिक", + "store": "इकट्ठा करना", + "admin": "व्यवस्थापक", + "member": "सदस्य", + "guest": "अतिथि", + "submit": "जमा करना", + "claim": "दावा", + "complete": "पूरा", + "shareTo": "को साझा करें", + "copyLink": "लिंक की प्रतिलिपि करें", + "faceBook": "फेसबुक", + "whatsApp": "Whatsapp", + "snapChat": "Snapchat", + "taskNamePersonalGameConsume": "खेल खर्च", + "taskNameRoomNewMember": "नये कक्ष सदस्य", + "taskNameRoomOwnerSendRedPacket": "कमरे का मालिक एक लाल पैकेट भेजता है", + "taskNameRoomOwnerSendGiftUser": "कमरे का मालिक उपहार भेजता है", + "taskNameRoomMicUser120Min": "माइक पर 120+ मिनट वाले सदस्य", + "taskNameRoomMicUser60Min": "माइक पर 60+ मिनट वाले सदस्य", + "taskNameRoomMicUser30Min": "माइक पर 30+ मिनट वाले सदस्य", + "taskNamePersonalSendGift": "उपयोगकर्ता को उपहार भेजें", + "taskNameRoomOnlineUserCount": "कक्ष ऑनलाइन सदस्य", + "taskNameRoomOwnerInviteMic": "सदस्य को माइक पर आमंत्रित करें", + "taskNameRoomUserSendGiftGold": "सदस्यों द्वारा उपहार में दिये गये सिक्के", + "taskNameRoomOwnerSendGiftGold": "कमरे के मालिक द्वारा उपहार में दिए गए सिक्के", + "taskNamePersonalMagicGiftGold": "जादुई उपहार भेजकर जीते गए सिक्के", + "taskNamePersonalLuckyGiftGold": "भाग्यशाली उपहार भेजकर जीते गए सिक्के", + "taskNameRoomOwnerMicTime": "रूम मालिक कमरे में माइक पर चला जाता है", + "taskNamePersonalActiveInRoom": "दूसरों के कमरों में सक्रिय रहें", + "taskNamePersonalMicInRoom": "माइक पर जाओ", + "dailyCoinBonanzaRulesDetail": "1. दैनिक व्यक्तिगत कार्य और दैनिक कक्ष स्वामी कार्य प्रति उपयोगकर्ता प्रति दिन एक बार पूरा किया जा सकता है। कार्य सऊदी समय 00:00:00 पर रीसेट हो गए।\n2.दैनिक व्यक्तिगत कार्य केवल दूसरों के कमरे में ही पूरे किये जा सकते हैं; कमरे के मालिक के कार्य केवल आपके अपने कमरे में ही पूरे किए जा सकते हैं।\n3. उपहार देने के कार्य केवल कमरों में भेजे गए उपहारों की गणना करते हैं, फ़ीड पर भेजे गए उपहारों की नहीं।\n4. यदि किसी भी तरह से एक ही डिवाइस या एक ही सिम कार्ड का उपयोग करके कई खाते बनाए जाते हैं, तो सभी कार्यों के लिए पुरस्कार का दावा केवल एक बार किया जा सकता है।", + "dailyCoinBonanzaRules": "दैनिक सिक्का बोनान्ज़ा नियम", + "roomOwnerTasks": "कक्ष स्वामी कार्य", + "personalTasks": "व्यक्तिगत कार्य", + "noPromptsToday": "आज कोई संकेत नहीं.", + "getPaidToRefer": "रेफर करने के लिए भुगतान प्राप्त करें", + "membershipFee": "मेम्बरशिप फीस", + "membershipFeeTips1": "कृपया अपने कमरे के लिए सदस्यता शुल्क निर्धारित करें। उपयोगकर्ता शुल्क का भुगतान करके आपके कमरे से जुड़ सकते हैं।", + "membershipFeeTips2": "उपयोगकर्ता को कमरे का सदस्य बनने के लिए आवश्यक सोना। कमरे के मालिक को 50% सोना मिलेगा।", + "freePrice": "शुल्क: 0-10000", + "touristsSendText": "पर्यटक पाठ भेजें", + "touristsTakeToTheMic": "पर्यटक माइक पर आते हैं", + "theMembershipFee": "सदस्यता शुल्क", + "theModificationsMade": "इस बार किए गए संशोधन बाहर निकलने के बाद सहेजे नहीं जाएंगे", + "viewFrame": "फ़्रेम देखें", + "enterRoomName": "कमरे का नाम दर्ज करें", + "headdress": "फ्रेम्स", + "mountains": "वाहनों", + "purchaseIsSuccessful": "खरीदारी सफल है", + "buy": "खरीदना", + "followed": "पालन ​​किया", + "follow2": "अनुसरण करें:{1}", + "fans2": "प्रशंसक:{1}", + "vistors2": "आगंतुक:{1}", + "personal2": "निजी:", + "family2": "परिवार:", + "conntinue": "जारी रखें", + "confirmBuyTips": "क्या आप वाकई खरीदना चाहते हैं?", + "purchase": "खरीदना", + "setRoomPassword": "कमरे का पासवर्ड सेट करें", + "inputRoomPassword": "इनपुट रूम पासवर्ड", + "enter": "प्रवेश करना", + "createDynamicSuccess": "डायनामिक सफलतापूर्वक बनाया गया", + "deleteDynamicTips": "क्या आप वाकई इस डायनामिक को हटाना चाहते हैं?", + "deleteCommentTips": "क्या आप इस कमेंट को मिटाने के बारे में पक्के हैं?", + "deleteSuccessful": "हटाना सफल!", + "itemsLeft": "आइटम बचे हैं", + "password": "पासवर्ड", + "replySucc": "उत्तर सफल", + "comment": "टिप्पणी", + "showMore": "और दिखाएँ", + "showLess": "कम दिखाओ", + "enterPassword": "पास वर्ड दर्ज करें", + "enterAccount": "खाता दर्ज करें", + "logIn": "लॉग इन करें", + "saySomething": "कुछ कहो...", + "sayHi": "नमस्ते कहे..", + "pleaseChatFfriendly": "कृपया दोस्ताना बातचीत करें", + "unLockTheRoom": "कमरे का ताला खोलो", + "operationSuccessful": "ऑपरेशन सफल रहा.", + "adminByHomeowner": "गृहस्वामी द्वारा प्रशासक के रूप में स्थापित किया गया है।", + "memberByHomeowner": "गृहस्वामी द्वारा सदस्यों के रूप में निर्धारित किया जाता है।", + "touristByHomeowner": "गृहस्वामी द्वारा एक पर्यटक के रूप में स्थापित किया गया है।", + "becomeHost": "मेज़बान बनने के लिए आवेदन करें", + "superFans": "सुपर प्रशंसक:", + "setUpAnIdentity": "एक पहचान स्थापित करें", + "kickedOutOfRoom": "कमरे से बाहर निकाल दिया", + "playGiftMusicAndDynamicMusic": "उपहार संगीत और गतिशील संगीत बजाएं", + "knapsack": "बस्ता", + "bdLeader": "बीडी नेता", + "picture": "चित्र", + "theImageSizeCannotExceed": "अपलोड विफल: फ़ाइल 2एमबी से कम होनी चाहिए।", + "activity": "गतिविधि", + "alreadyAnAdministrator": "पहले से ही प्रशासक है", + "alreadyAnMember": "पहले से ही सदस्य है", + "alreadyAnTourist": "पहले से ही एक पर्यटक", + "touristsCannotSendMessages": "पर्यटक संदेश नहीं भेज सकते", + "touristsAreNotAllowedToGoOnTheMic": "पर्यटकों को माइक पर जाने की इजाजत नहीं है", + "lockTheRoom": "कमरा बंद करो", + "special": "विशेष", + "visitorList": "आगंतुक सूची", + "successfulWear": "सफल पहनावा", + "confirmUnUseTips": "क्या आप इसे हटाने की पुष्टि करते हैं?", + "custom": "रिवाज़", + "myItems": "मेरे आइटम", + "use": "उपयोग", + "unUse": "उपयोग न करें", + "renewal": "नवीनीकरण", + "wallet": "बटुआ", + "profile": "प्रोफ़ाइल", + "giftwall": "गिफ्टवॉल", + "announcement": "घोषणा", + "blockedList": "अवरुद्ध सूची", + "country2": "देश:", + "sendTo": "भेजना", + "medals": "पदक", + "activityMedal": "गतिविधि पदक", + "achievementMedal": "उपलब्धि पदक", + "credits": "श्रेय: {1}", + "successfullyUnloaded": "सफलतापूर्वक अनलोड किया गया", + "expired": "खत्म हो चुका", + "day": "दिन", + "inUse": "उपयोग में", + "confirmUseTips": "क्या आप इसके उपयोग की पुष्टि करना चाहते हैं?", + "pleaseUploadUserAvatar": "कृपया एक अवतार अपलोड करें.", + "joinMemberTips": "यदि आप कमरे में आगंतुक हैं, तो आप माइक्रोफ़ोन नहीं ले जा सकते।", + "giftGivingSuccessful": "उपहार देना सफल.", + "theAccountPasswordCannotBeEmpty": "खाता या पासवर्ड खाली नहीं हो सकता.", + "invitesYouToTheMicrophone": "{1} आपको माइक्रोफ़ोन पर आमंत्रित करता है", + "english": "अंग्रेज़ी", + "chinese": "चीनी", + "arabic": "अरबी", + "darkMode": "डार्क मोड", + "lightMode": "लाइट मोड", + "systemDefault": "प्रणालीगत चूक", + "pleaseGetOnTheMicFirst": "कृपया पहले माइक पर आएँ।", + "duration2": "अवधि:{1}" +} diff --git a/assets/l10n/intl_id.json b/assets/l10n/intl_id.json new file mode 100644 index 0000000..96e65b3 --- /dev/null +++ b/assets/l10n/intl_id.json @@ -0,0 +1,777 @@ +{ + "signInWithGoogle": "Masuk dengan Google", + "or": "Atau", + "signInWithYourAccount": "Masuk dengan akun Anda", + "signInWithApple": "Masuk dengan Apple", + "loginRepresentsAgreementTo": "Login mewakili persetujuan untuk", + "termsofService": "Persyaratan layanan", + "privaceyPolicy": "Kebijakan Privasi", + "tips": "Kiat", + "dailyTasksTips": "Catatan: Siap untuk menangani tugas? Temukan di kamar favorit Anda.", + "searchNoDataTips": "Masukkan ruangan atau ID pengguna yang ingin Anda cari.", + "youHaveNotHadVIPYet": "Anda belum memiliki VIP, datang dan coba", + "games": "Pertandingan", + "vipAccelerating": "{1} Mempercepat", + "mine": "Milikku", + "luckGiftRuleTips": "Berikan hadiah keberuntungan dan menangkan hadiah koin emas hingga 1000 kali lipat! Pengguna yang menerima hadiah keberuntungan dapat menikmati keuntungan berikut: \n(1) Bagi pengguna yang menerima hadiah keberuntungan: +4% nilai pengalaman pesona berdasarkan nilai hadiah. 
(2) Jika penerima adalah tuan rumah: +4% nilai target gaji tuan rumah berdasarkan nilai hadiah.", + "badge": "Lencana", + "vipRippleTheme": "Tema Riak VIP", + "glory": "Kejayaan", + "badgeHonor": "Lencana/Kemuliaan", + "party": "Berpesta", + "other": "Lainnya", + "gifProfileUpload": "Unggah Profil GIF", + "levelMedal": "Medali Tingkat", + "levelIcon": "Ikon Tingkat", + "maliciousHarassment": "Pelecehan yang berbahaya", + "event": "Peristiwa", + "roomEdit": "Pengeditan Ruangan", + "cancelRoomPassword": "Apakah Anda yakin ingin menghapus kata sandi ruangan?", + "roomMemberFee": "Biaya Anggota Kamar", + "roomTheme2": "Tema Kamar", + "blockedList2": "Daftar yang Diblokir", + "roomPassword": "Kata Sandi Kamar", + "numberOfMic": "Jumlah Mikrofon", + "pleaseEnterContent": "Silakan masukkan konten", + "profilePhoto": "Foto Profil", + "aboutMe": "Tentang saya", + "myRoom": "Kamarku", + "noHistoricalRecordsAvailable": "Tidak ada catatan sejarah yang tersedia.", + "inviteNewUsersToEarnCoins": "Undang pengguna baru untuk mendapatkan koin", + "crateMyRoom": "Buat kamarku", + "vipSpecialGiftTassel": "Rumbai hadiah spesial VIP", + "vipEntranceEffect": "Efek Masuk VIP", + "casualInteraction": "Interaksi Santai", + "haveGamePlayingTips": "Anda memiliki permainan yang sedang berlangsung. Silakan keluar dari permainan saat ini. Apakah Anda yakin ingin keluar?", + "historicalTour": "Wisata Sejarah", + "gameCenter": "Pusat Permainan", + "returnToVoiceChat": "Kembali ke obrolan suara?", + "exitGameMode": "Keluar dari Mode Permainan", + "enterTheRoom": "Masuki ruangan", + "invite": "Mengundang", + "inviteGoRoomTips": "Selalu di sini untukmu, hujan atau cerah. Mampirlah dan sapa!", + "confirmInviteThisUserToTheRoom": "Konfirmasikan undangan pengguna ini (ID:{1}) ke ruang?", + "honor": "Menghormati", + "clearCacheSuccessfully": "Hapus cache berhasil", + "sent": "Terkirim", + "keep": "Menyimpan", + "open": "Membuka", + "deleteAccountTips2": "*Jika Anda berubah pikiran, Anda dapat masuk kembali ke akun Anda saat ini dalam tujuh hari, dan kami akan memulihkan akun Anda secara otomatis. Jika tidak ada pemulihan yang terjadi dalam waktu tujuh hari, akun akan dihapus secara permanen", + "deleteAccountTips": "Anda memiliki hak administratif penuh atas akun ini. Jika Anda bermaksud menghapus akun, harap perhatikan risiko berikut yang terkait dengan operasi ini:\n1.Setelah akun berhasil dihapus, Anda tidak dapat lagi masuk ke akun Anda saat ini. Penghapusan akun merupakan tindakan permanen.\n2. Setelah akun berhasil dihapus, Anda tidak akan dapat memulihkan data akun apa pun. Semua informasi (termasuk kamar, teman), mata uang virtual, hadiah, dan barang virtual akan dihapus secara permanen dan tidak dapat dipulihkan.\n3. Masa tunggu: Jika Anda tidak memulihkan akun, Anda tidak akan dapat mengakses halaman pembelian, halaman penarikan, atau halaman lainnya di aplikasi.\n4.Selama masa tunggu atau setelah akun dihapus, halaman profil akan menunjukkan bahwa akun tersebut telah dihapus. Untuk melindungi akun Anda agar tidak dicari atau diakses oleh orang lain, informasi pribadi Anda akan dihapus dari sistem yang terkait dengan fungsi sehari-hari. Jika penghapusan akun melibatkan keamanan nasional, proses perdata atau pidana, atau perlindungan hak dan kepentingan sah pihak ketiga, pejabat berhak menolak permintaan penghapusan akun pengguna.\nJika Anda yakin ingin menghapus semua data pribadi dari akun Anda saat ini, silakan klik \"Hapus Akun\".", + "accountDeletionNotice": "Pemberitahuan Penghapusan Akun:", + "thisUserHasBeenBlacklisted": "Pengguna ini telah masuk daftar hitam.", + "trend": "Kecenderungan", + "like": "Menyukai", + "more": "Lagi", + "discard": "Membuang", + "catchFirstComment": "Tangkap komentar pertama", + "reply": "Membalas", + "posting": "Posting", + "dynamic": "Dinamis", + "multiple": "Banyak", + "successfullyAddedToTheDynamicBlacklist": "Berhasil ditambahkan ke daftar hitam dinamis!", + "successfullyRemovedFromTheBlacklist": "Berhasil dihapus dari daftar hitam!", + "successfullyRemovedFromTheDynamicBlacklist": "Berhasil dihapus dari daftar hitam dinamis!", + "successfullyAddedToTheBlacklist": "Berhasil ditambahkan ke daftar hitam!", + "youAreCurrentlyCPRelationshipPleaseDissolve": "Anda sedang menjalin hubungan CP.\nSilakan dilarutkan terlebih dahulu.", + "areYouSureToCancelBlacklist": "Apakah Anda yakin untuk membatalkan daftar hitam?", + "areYouSureYouWantToBlockThisUser": "Apakah Anda yakin ingin memblokir pengguna ini?", + "areYouSureYouWantToDynamicBlockThisUser": "Apakah Anda yakin untuk menambahkan daftar hitam dinamis?", + "removeFromBlacklist": "Hapus dari daftar hitam", + "moveToBlacklist": "Pindah ke daftar hitam", + "userBlacklist": "Daftar hitam pengguna", + "specialEffectsManagement": "Manajemen efek khusus", + "wishingYouHappinessEveryDay": "Semoga Anda bahagia setiap hari.", + "newMessage": "Pesan baru", + "createRoomSuccsess": "Ciptakan kesuksesan ruangan!", + "contactUs": "Hubungi saya", + "systemAnnouncementTips1": "Penjagaan terhadap penipuan:", + "systemAnnouncementTips": "Verifikasi informasi hanya melalui saluran resmi. Jangan pernah mengunduh perangkat lunak pihak ketiga, membagikan data pribadi, atau mentransfer uang berdasarkan permintaan eksternal. ID staf resmi hanya 10000, 10003, dan 10086. Jika ada kecurigaan, hentikan dan laporkan melalui", + "systemAnnouncement": "Pengumuman Sistem", + "doNotClickUnfamiliarTips": "Jangan klik tautan yang tidak dikenal, karena dapat mengungkap informasi pribadi Anda. Jangan pernah membagikan detail ID atau kartu bank Anda kepada siapa pun.", + "atTag": "@Menandai", + "sayHi2": "Katakan Hai", + "canSendMsgTips": "Kedua belah pihak harus saling mengikuti sebelum mereka dapat mengirim pesan pribadi.", + "msgSendRedEnvelopeTips": "*Biaya layanan sebesar 10% akan dikenakan pada amplop merah, dan penerima hanya akan menerima 90% dari nilai amplop merah. Tingkat kekayaan pengirim harus lebih tinggi dari Level 10.", + "leavFamilyTips": "Apakah Anda yakin ingin keluar dari klan Anda saat ini? Anda harus menunggu 1 hari untuk bergabung kembali dengan klan.", + "leavingTheFamily": "Meninggalkan keluarga", + "familyNotifcations": "Pemberitahuan Keluarga", + "familyNews": "Berita Keluarga", + "reapply": "Melamar lagi", + "cancelRequestFamilyMsg": "Permintaan Anda untuk bergabung dengan keluarga 【keluarga {1}】 sedang ditinjau, apakah Anda yakin untuk membatalkan permintaan tersebut?", + "cancelRequest": "Batalkan Permintaan", + "pending": "Tertunda", + "familyAnnouncement": "Pengumuman Keluarga", + "enterFamilyAnnouncement": "Silakan masukkan pengumuman keluarga.", + "disbandTheFamily": "Bubarkan keluarga", + "editFamily": "Sunting Keluarga", + "supporter": "Pendukung", + "rechargeSuccessful": "Isi Ulang Berhasil", + "transactionReceived": "Transaksi Diterima", + "createFamilySuccess": "Ciptakan kesuksesan keluarga.", + "numberOfSign": "Jumlah tanda: {1}", + "hostWeeklyRank": "Peringkat Mingguan Tuan Rumah", + "supporterWeeklyRank": "Peringkat Mingguan Suporter", + "memberList": "Daftar Anggota", + "treasureChest": "Peti Harta Karun", + "xxfamily": "keluarga {1}", + "applicationRecord": "Catatan Aplikasi", + "createFamily": "Buat Keluarga", + "familyName": "Nama keluarga", + "createAFamily": "Ciptakan sebuah keluarga", + "searchFamilyIdHint": "Silakan masukkan ID pemilik keluarga", + "enterFamilyInfo": "Silakan perkenalkan secara singkat keluarga Anda!", + "enterFamilyName": "Silakan masukkan nama keluarga Anda.", + "familyInfo": "Perkenalan keluarga", + "joinFamily": "Bergabunglah dengan sebuah keluarga", + "appUpdateTip": "Aplikasi ini memiliki versi baru ({1}), silakan buka dan unduh?", + "ownerIncomeCoins": "Pendapatan pemilik:{1} koin", + "game": "Permainan", + "skip2": "Melewati", + "coins4": "Koin", + "currentVip": "VIP saat ini", + "weekStart": "Awal Minggu", + "forMoreRewardsPleaseCheckTheTaskCenter": "Untuk hadiah lebih lanjut, silakan periksa pusat tugas", + "kingQuuen": "Raja-Ratu", + "ramadan": "Ramadan", + "updateNow": "Perbarui Sekarang", + "allGames": "Semua Permainan", + "fishClass": "Kelas Ikan", + "greedyClass": "Kelas serakah", + "raceSeries": "Seri Balapan", + "slotsClass": "Kelas Slot", + "others": "Yang lain", + "hotGames": "Permainan Panas", + "chatBox": "Kotak obrolan", + "termsOfServicePrivacyPolicyTips": "Dengan melanjutkan, Anda menyetujui Ketentuan Layanan & Kebijakan Privasi", + "and": "Dan", + "pleaseSelectTheTypeContent": "Silakan pilih jenis konten yang menyinggung.", + "wearHonor": "Pakailah Kehormatan", + "illegalInformation": "Informasi ilegal", + "inappropriateContent": "Konten yang tidak pantas", + "personalAttack": "Serangan pribadi", + "confirm": "Mengonfirmasi", + "spam": "Spam", + "countdownMinutes": "Menit Hitung Mundur:", + "number2": "Nomor:", + "fraud": "Tipuan", + "received": "Diterima", + "currentProgress": "Kemajuan saat ini", + "currentStage": "Tahap saat ini:{1}", + "roomReward2": "Hadiah Kamar:{1}", + "roomReward": "Hadiah Kamar", + "expirationTime": "Waktu kedaluwarsa", + "ownerSendTheRedEnvelope": "Pemilik mengirimkan koin Hadiah.", + "rewardCoins": "Koin hadiah:{1} koin", + "lastWeekProgress": "Kemajuan minggu lalu", + "redEnvelopeTips2": "*Jika tas merah tidak diklaim dalam batas waktu, sisa koin akan dikembalikan kepada pengguna yang mengirimkan tas merah.", + "goToRecharge": "Pergi untuk mengisi ulang", + "deleteAccount2": "Hapus Akun({1}s)", + "areYouSureYouWantToDeleteYourAccount": "Apakah Anda yakin ingin menghapus akun Anda?", + "insufhcientGoldsGoToRecharge": "Emas tidak mencukupi, isi ulang sekarang!", + "coins2": "{1}Koin", + "remainingNumberTips": "Jumlah yang tersisa tersedia:({1}/{2})", + "collectionTimeTips": "Waktu pengambilan:{1}({2}/{3})", + "sendARedEnvelope": "Kirim amplop merah", + "sendRedPackConfirmTips": "Apakah Anda yakin ingin mengirim paket merah?", + "redEnvelopeSendingRecords": "Catatan pengiriman amplop merah:", + "redEnvelope": "Amplop Merah", + "redEnvelopeRecTips2": "Semua amplop merah telah diklaim.", + "redEnvelopeRecTips3": "Waktu pengambilan amplop merah telah habis!", + "openTheTreasureChest": "Mengirimkan tas merah!", + "redEnvelopeRecTips1": "Koin yang diperoleh telah disimpan ke dompet Anda.", + "redEnvelopeTips1": "Koin:", + "roomTools": "Peralatan Ruangan:", + "entertainment": "Hiburan:", + "reportSucc": "Laporkan berhasil", + "pornography": "Pornografi", + "reportInputTips": "Mohon jelaskan permasalahannya sedetail mungkin agar kami dapat memahami dan menyelesaikannya.", + "cancel": "Membatalkan", + "join": "Bergabung", + "items": "Barang", + "vistors": "Pengunjung", + "fans": "Penggemar", + "balanceNotEnough": "Saldo koin emas tidak mencukupi. Apakah Anda ingin pergi ke top up?", + "skip": "Lewati {1}", + "wearMedal": "Pakai Medali", + "activityHonor": "Kehormatan Aktivitas", + "achievementHonor": "Kehormatan Prestasi", + "youDontHaveAnyHonorYet": "Anda belum mendapat kehormatan apa pun.", + "letGoToWatch": "Ayo pergi menonton!", + "launchedARocket": "meluncurkan roket", + "sendUserId": "Kirim ID Pengguna:{1}", + "giveUpIdentity": "Menyerahkan identitas", + "leaveRoomIdentityTips": "Apakah Anda yakin ingin melepaskan identitas kamar?", + "joinMemberTips2": "Apakah Anda ingin mengonfirmasi bergabung dengan ruang sebagai anggota?", + "sureUnfollowThisRoom": "Yakin untuk berhenti mengikuti ruangan ini?", + "welcomeMessage": "Selamat datang di aplikasi kami, {name}!", + "settings": "Pengaturan", + "account": "Akun", + "common": "Umum", + "delete": "Menghapus", + "copy": "Menyalin", + "bio": "Biografi", + "useCoupontips": "Apakah Anda yakin ingin menggunakan kupon tersebut?", + "searchUserId": "Cari ID pengguna", + "sendUser": "Kirim Pengguna", + "hobby": "Hobi", + "sendCoupontips": "Apakah Anda yakin ingin mengirimkan kupon ini kepada pengguna ini?", + "youDontHaveAnyCouponsYet": "Anda belum memiliki kupon apa pun.", + "recall": "Mengingat", + "youHaventFollowed": "Anda belum mengikuti ruangan mana pun", + "deleteFromMyDevice": "Hapus dari perangkat saya", + "deleteOnAllDevices": "Hapus di semua perangkat", + "messageHasBeenRecalled": "Pesan ini telah ditarik kembali", + "recallThisMessage": "Ingat pesan ini?", + "language": "Bahasa", + "feedback": "Masukan", + "signedin": "Masuk", + "receiveSucc": "Berhasil diklaim", + "about": "Tentang", + "aboutUs": "Tentang Kami", + "theme": "Tema", + "wealthLevel": "Tingkat Kekayaan", + "userLevel": "Tingkat Pengguna", + "goToUpload": "Pergi untuk mengunggah", + "logout": "Keluar", + "luck": "Keberuntungan", + "level": "Tingkat", + "vip": "VIP", + "vip1": "VIP1", + "vip2": "VIP2", + "vip3": "VIP3", + "vip4": "VIP4", + "vip5": "VIP5", + "vip6": "VIP6", + "themeGoToUploadTips": "1.Tinjau dalam waktu 24 jam setelah pengunggahan berhasil.\n2.Semua koin akan dikembalikan jika peninjauan gagal.", + "home": "Rumah", + "explore": "Mengeksplorasi", + "me": "Aku", + "socialPrivilege": "Hak istimewa sosial", + "information": "Informasi", + "myPhoto": "Foto saya", + "cpRequest": "Permintaan CP", + "areYouSureYouWantToSpend3": "*Jika pihak lain menolak undangan CP, koin Anda akan dikembalikan ke dompet Anda.", + "areYouSureYouWantToSpend": "Apakah Anda yakin ingin membelanjakannya", + "areYouSureYouWantToSpend2": "untuk mengirim undangan CP ke pengguna ini?", + "cpSexTips": "Pasangan dengan jenis kelamin yang sama tidak dapat diciptakan.", + "underReview": "Sedang ditinjau", + "doYouWantToDeleteIt": "Apakah Anda ingin menghapusnya?", + "chooseFromAblum": "Pilih dari Ablum", + "spaceBackground": "Latar Belakang Luar Angkasa", + "editProfile": "Sunting Profil", + "sendTheCpRequest": "Kirim permintaan CP", + "addCp": "Tambahkan CP", + "partWays": "Bagian Cara", + "reconcile": "Mendamaikan", + "separated": "Terpisah", + "areYouSureYouWantToSpend5": "{1} menyatakan perasaannya kepadamu; jika kamu menerimanya, kamu akan menjadi pasangan.", + "areYouSureYouWantToSpend6": "{1} ingin kembali bersama Anda. Jika Anda memutuskan untuk melakukan rekonsiliasi, semua data Anda sebelumnya akan dipulihkan.", + "reconcileInvitationTips": "*Jika pihak lain menolak undangan CP, koin Anda akan dikembalikan ke dompet Anda.", + "reconcileInvitation": "Rekonsiliasi Undangan", + "areYouSureYouWantToSpend4": "mengirim undangan rekonsiliasi kepada pengguna ini?", + "partWaysTips": "*Jika salah satu pasangan memilih untuk berpisah, akan ada periode jeda selama 7 hari. Selama waktu ini, kedua belah pihak dapat memilih untuk melakukan rekonsiliasi, dan semua data akan dipulihkan setelah rekonsiliasi. Jika tidak ada rekonsiliasi yang dipilih pada akhir periode 7 hari, data pasangan tersebut akan dihapus.", + "areYouSureYouWantToPartWaysWithYourCP": "Apakah Anda yakin ingin berpisah dengan CP Anda?", + "timeSpentTogether": "Waktu yang dihabiskan bersama: {1} hari", + "firstDay": "Hari pertama:{1}", + "numberOfMyCPs": "Jumlah CP saya:({1}/{2})", + "props": "Alat peraga", + "medal": "Medali", + "win": "Menang", + "dice": "Dadu", + "rps": "RPS", + "areYouSureToCancelDynamicBlacklist": "Apakah Anda yakin untuk membatalkan daftar hitam dinamis?", + "blockUserDynamic": "Blokir Dinamis Pengguna", + "unblockUserDynamic": "Buka blokir Dinamis Pengguna", + "operationFail": "Operasi itu gagal.", + "likedYourComment": "Menyukai Komentar Anda.", + "likedYourDynamic": "Menyukai Dinamis Anda.", + "doYouWantToKeepTheDraft": "Apakah Anda ingin menyimpan drafnya?", + "cantSendDynamicTips": "Anda saat ini diblokir dari posting Dynamics.", + "operationsAreTooFrequent": "Operasi terlalu sering", + "luckNumber": "Nomor Keberuntungan", + "relationShip": "Hubungan", + "couple": "Pasangan {1}:", + "couple2": "Pasangan", + "reject": "Menolak", + "cpList": "Daftar CP", + "sound2": "Suara", + "hot": "Panas", + "selectCountry": "Pilih negara", + "giftEffect": "Efek Hadiah", + "winFloat": "Menangkan Mengambang", + "giftVibration": "Efek Hadiah Keberuntungan", + "accept": "Menerima", + "noMatchedCP": "Tidak ada CP yang cocok", + "inviteYouToBecomeBD": "Mengundang Anda untuk menjadi BD.", + "adminInviteRechargeAgent": "Mengundang Anda untuk menjadi agen Isi Ulang.", + "confirmAcceptTheInvitation": "Konfirmasikan untuk menerima undangan?", + "confirmDeclineTheInvitation": "Konfirmasi penolakan undangan?", + "host": "Tuan rumah", + "following": "Mengikuti", + "agent": "Agen", + "approved": "Disetujui", + "agreeJoinFamilyTips": "Apakah Anda mengizinkan pengguna ini bergabung dengan keluarga?", + "refuseJoinFamilyTips": "Haruskah Anda menolak membiarkan pengguna bergabung dengan keluarga?", + "onlineUsers": "Pengguna Daring ({1}/{2}):", + "applyToJoin": "Ajukan permohonan untuk bergabung", + "supporterList": "Daftar Pendukung", + "hostList": "Daftar Tuan Rumah", + "upToAdmins": "Hingga {1} Admin", + "upToMembers": "Hingga {1} Anggota", + "levelPrivileges": "Hak istimewa tingkat", + "familyLevel": "Tingkat Keluarga", + "kickOutOfFamily": "Keluar dari keluarga", + "disbandTheFamilyTips": "Apakah Anda yakin ingin membubarkan keluarga?", + "setAsFamilyAdmin": "Tetapkan sebagai admin keluarga", + "cancelFamilyAdmin": "Batalkan admin keluarga", + "kickFamilyUserTips": "Apakah Anda yakin ingin mengeluarkan pengguna ini dari keluarga?", + "familyMember2": "Anggota Keluarga ({1}/{2}):", + "familyMember3": "Anggota Keluarga ({1}):", + "familyAdmin2": "Admin Keluarga ({1}/{2}):", + "familyOwner2": "Pemilik Keluarga:", + "ra": "RA", + "roomAnnouncement": "Pengumuman Kamar", + "family3": "{1} s'Keluarga", + "help": "Membantu", + "rejected": "Ditolak", + "boxContributeTips": "Investasi sudah dilakukan hari ini, mohon jangan berinvestasi lagi", + "familyHelpTips": "(1)Jika Anda menginvestasikan 50 koin, peti harta karun pertama akan terbuka ketika 5 orang berinvestasi. Anda dapat mengklik untuk menerima 50 koin. (2) Peti harta karun kedua akan terbuka ketika 10 orang berinvestasi. Anda dapat menerima 50 koin.\n(3) Peti harta karun kedua akan terbuka ketika 20 orang berinvestasi. Anda dapat menerima 100 koin.\n(4) Jika jumlah pengguna yang diperlukan untuk mengklaim peti harta karun tidak tercapai pada hari yang sama, kemajuan peti harta karun akan diatur ulang pada hari berikutnya.\n(5) Kemajuan peti harta karun akan diatur ulang pada hari berikutnya. Pengguna yang belum mengklaim peti harta karunnya pada hari yang sama harus mengklaimnya tepat waktu.\n(6) Waktu reset peti harta karun: 00:00 waktu Saudi.", + "bd": "BD", + "coupon": "Kupon", + "search": "Mencari", + "get": "Mendapatkan", + "inRocket": "Di dalam roket", + "roomRocketHelpTips": "1. Mengirim hadiah di dalam ruangan meningkatkan energi roket. *1 hadiah koin emas = 1 poin energi roket; hadiah keberuntungan meningkatkan energi roket sebesar 4% dari nilai koin emas hadiah.\n2. Setelah energi roket terisi penuh, ruangan dapat meluncurkan roket. Hadiah akan didistribusikan secara otomatis setelah peluncuran.\n3. Level roket yang berbeda menawarkan hadiah yang berbeda.\n4. Saat roket diluncurkan, semua pengguna di ruangan dapat mengklaim hadiah roket.5. Energi roket disetel ulang pada pukul 00:00 setiap hari.", + "roomRocketRecordAction": "Catatan", + "roomRocketRewardTop1": "Top1", + "roomRocketRewardSetOff": "Diluncurkan", + "roomRocketRewardInRoom": "Di dalam ruangan", + "roomRocketResetCountdown": "23 jam, 59 menit, dan 59 detik hingga reset", + "roomRocketRecordRoom": "Ruangan", + "roomRocketRecordReward": "Hadiah", + "roomRocketRecordLast35Days": "Hanya tampilkan catatan 35 hari terakhir", + "couponRecord": "Catatan penggunaan kupon", + "inRoom": "Di Kamar", + "searchCouponHint": "Cari Kupon", + "giftCounter": "Penghitung hadiah", + "bDLeaderInviteYouToBecomeBDLeader": "Mengundang Anda untuk menjadi BDLeader", + "wins": "menang", + "inviteYouToBecomeHost": "Mengundang Anda untuk menjadi tuan rumah.", + "friends": "Teman-teman", + "deleteConversationTips": "Apakah Anda yakin ingin menghapus riwayat obrolan dengan pengguna ini?", + "propMessagePrompt": "Prompt pesan prop", + "inputUserId": "Masukkan ID Pengguna", + "fromLuckyGifts": "dari hadiah keberuntungan", + "receive": "Menerima", + "checkInSuccessful": "Check-in berhasil", + "sginTips": "Anda akan mendapatkan hadiah saat pertama kali login setiap hari. Jika Anda menghentikan proses login, hadiah akan dihitung mulai hari pertama saat Anda login kembali.", + "vipBadge": "Lencana VIP", + "vipProfileFrame": "Bingkai Profil VIP", + "vipProfileCard": "Kartu Profil VIP", + "popular": "Populer", + "recommend": "Menyarankan", + "follow": "Mengikuti", + "history": "Sejarah", + "hotRooms": "Kamar Panas", + "viewMore": "Lihat lebih banyak", + "noData": "Tidak ada data", + "users": "Pengguna", + "rooms": "Kamar", + "coins": "koin", + "unread": "Belum dibaca", + "read": "Membaca", + "image": "[Gambar]", + "video": "[Video]", + "sound": "[Suara]", + "gift2": "[Hadiah]", + "clickHereToStartChatting": "Katakan sesuatu.....", + "receivedAMessage": "[Menerima pesan]", + "confirmSwitchMicModelTips": "Apakah Anda mengonfirmasi peralihan dalam mode tempat duduk?", + "number": "Nomor", + "album": "Album", + "camera": "Kamera", + "system": "Sistem", + "notifcation": "Melihat", + "inviteYouToBecomeAgent": "Mengundang Anda untuk menjadi agensi.", + "myMusic": "Musik Saya", + "add": "Menambahkan", + "pullToLoadMore": "Tarik untuk memuat lebih banyak", + "loadingFailedClickToRetry": "Gagal memuat, klik untuk mencoba lagi", + "releaseToLoadMore": "Lepaskan untuk memuat lebih banyak", + "haveMyLimits": "---Aku punya batasku.---", + "music": "Musik", + "free": "Bebas", + "charm": "Pesona hadiah", + "start": "Awal", + "vipUseThisThemeTips": "Hanya pengguna yang memenuhi level VlP yang dapat menggunakan tema ini.", + "stop": "Berhenti", + "chats": "Obrolan", + "family": "Keluarga", + "refuse": "Menolak", + "agree": "Setuju", + "thisFeatureIsCurrentlyUnavailable": "Fitur ini saat ini tidak tersedia.", + "pleaseSelectTheRecipient": "Silakan pilih penerima.", + "searchMemberIdHint": "Silakan masukkan ID anggota", + "unclaimedRedEnvelopes": "Amplop merah yang tidak diambil akan dikembalikan dalam 24 jam.", + "redEnvelopeNotYetClaimed": "Amplop merah belum diklaim.", + "redEnvelopeAmount": "Jumlah amplop merah: {1} koin", + "sentARedEnvelope": "mengirim amplop merah.", + "theRedEnvelopeHasExpired": "Amplop merah sudah habis masa berlakunya.", + "joinRequest": "Permintaan Gabung", + "welcomeToMyFamily": "Selamat datang di keluarga saya!", + "scrollToTheBottom": "Gulir ke bawah", + "gameRules": "Aturan Permainan:", + "charmGameRulesTips": "Aktifkan dasbor untuk menampilkan koin hadiah yang diterima semua pengguna di mikrofon, 1 koin = 1 skor (Hadiah keberuntungan 1 koin = skor 0,04).", + "inputYourOldPassword": "Masukkan kata sandi lama Anda", + "enterYourOldPassword": "Masukkan kata sandi lama Anda", + "setYourPassword": "Tetapkan kata sandi Anda", + "enterYourNewPassword": "Masukkan kata sandi baru Anda", + "confirmYourPassword": "Konfirmasikan kata sandi Anda", + "theTwoPasswordsDoNotMatch": "Kedua kata sandi tidak cocok.", + "resetLoginPasswordtTips2": "Kata sandi harus terdiri dari 8-16 karakter dan harus berupa kombinasi huruf besar dan kecil serta angka dalam bahasa Inggris (bukan hanya angka)", + "resetLoginPassword": "Atur Ulang Kata Sandi Masuk", + "resetLoginPasswordtTips1": "Masuk dengan ID pengguna atau ID cantik Anda. Lebih aman masuk dengan akun Aslan.", + "localMusic": "Musik Lokal", + "setLoginPassword": "Tetapkan Kata Sandi Masuk", + "confirmSwitchMicThemeTips": "Apakah Anda mengonfirmasi peralihan gaya kursi?", + "pleaseUpgradeYourVipLevelFirst": "Silakan tingkatkan level VIP Anda terlebih dahulu.", + "micTheme": "Tema Mikrofon", + "classicMic": "Klasik {1} Mikrofon", + "yesterday": "Kemarin {1}", + "monday": "Senin {1}", + "tuesday": "Selasa {1}", + "wednesday": "Rabu {1}", + "thursday": "Kamis {1}", + "friday": "Jumat {1}", + "saturday": "Sabtu {1}", + "sunday": "Minggu {1}", + "acceptedYour": "{1} menerima Anda", + "youAccepted": "Anda menyetujui {1}", + "openRedPackDialogTip": "Amplop merah", + "micManagement": "Manajemen Mikrofon", + "vipChatBox": "Kotak Obrolan VIP", + "vipRoomCoverBorder": "Perbatasan Penutup Ruang VIP", + "bdCenter": "Pusat BD", + "renewVip": "Perpanjang VIP", + "rechargeAgency": "Badan Isi Ulang", + "adminCenter": "Pusat Kerja", + "goldList": "Daftar Emas", + "followList": "Ikuti Daftar", + "fansList": "Daftar Penggemar", + "daily": "Sehari-hari", + "cp": "CP", + "female": "Perempuan", + "male": "Pria", + "identity": "Identitas", + "adjust": "Menyesuaikan", + "warning": "Peringatan", + "screenshotTips": "Tangkapan Layar (Hingga 3)", + "roomNotice": "Pemberitahuan kamar", + "roomTheme": "Tema kamar", + "description": "Keterangan:", + "inputDesHint": "Mohon jelaskan permasalahannya sedetail mungkin agar kami dapat memahami dan menyelesaikannya.", + "roomProfilePicture": "Gambar profil kamar", + "userProfilePicture": "Gambar profil pengguna", + "userName": "Nama belakang", + "pleaseSelectTheTypeToProcess": "Silakan pilih jenis yang akan diproses:", + "roomEditing": "Pengeditan Ruangan", + "setAccount": "Tetapkan Akun", + "userEditing": "Pengeditan Pengguna", + "enterTheUserId": "Masukkan ID pengguna", + "enterTheRoomId": "Masukkan ID ruangan", + "deleteAccount": "Hapus Akun", + "becomeAgent": "Menjadi agen", + "enterNickname": "Masukkan Nama Panggilan", + "selectYourCountry": "Pilih negara Anda", + "inviteCode": "Kode Undangan", + "magic": "Sihir", + "list": "Daftar", + "walletBalance": "Saldo dompet:", + "canOnlyBeShown": "*Hanya dapat ditampilkan kepada semua pengguna di wilayah Anda.", + "sendMessage": "Kirim Pesan", + "availableCountries": "Negara yang Tersedia:", + "currentBalance": "Saldo Saat Ini:", + "successfulTransaction": "{1} Transaksi Berhasil", + "hasBeenACoinAgencyForDays": "Telah Menjadi Agen Koin Selama {1} Hari", + "luckGiftSpecialEffects": "Efek animasi hadiah keberuntungan", + "theVideoSizeCannotExceed": "Ukuran video tidak boleh melebihi 50M", + "weekly": "Mingguan", + "rechargeRecords": "Isi Ulang Catatan", + "balance": "keseimbangan:", + "appStore": "Toko Aplikasi", + "searchCountry": "Telusuri negara...", + "googlePay": "Google Bayar", + "biggestDiscount": "Diskon terbesar", + "officialRechargeAgent": "Agen isi ulang resmi", + "customizedGiftRulesContent": "Cara mendapatkan hadiah khusus saya:\n1.Tentukan apakah pengguna berhak menerima hadiah khusus berdasarkan tingkat kekayaan mereka saat ini.\n(1) Ketika tingkat kekayaan pengguna ≥35: Pengguna dapat mengunggah video untuk hadiah khusus satu kali. Kami akan menggunakan gambar profil pengguna saat ini sebagai gambar hadiah dan video yang disediakan sebagai efek hadiah pada \"Disesuaikan\". Produksi akan memakan waktu beberapa saat, dan kami akan memberi tahu Anda segera setelah tersedia di toko.\n(2) Ketika tingkat kekayaan pengguna ≥45: Pengguna dapat mengunggah video untuk hadiah khusus satu kali. Kami akan menggunakan gambar profil pengguna saat ini sebagai gambar hadiah dan video yang disediakan sebagai efek hadiah pada \"Disesuaikan\". Produksi akan memakan waktu beberapa saat, dan kami akan memberi tahu Anda segera setelah tersedia di toko.\n2.Anda dapat berpartisipasi dalam aktivitas tertentu di aplikasi dan, setelah memenuhi kriteria, menghubungi kami melalui bagian \"Pesan\" → \"Hubungi kami\". Berikan tangkapan layar aktivitas dan video yang ingin Anda gunakan untuk hadiah khusus. Kami akan menggunakan gambar profil Anda saat ini sebagai gambar hadiah dan video yang disediakan sebagai efek hadiah, lalu mencantumkannya di bawah \"Disesuaikan\". Produksi akan memakan waktu beberapa saat, dan kami akan memberi tahu Anda segera setelah tersedia di toko.\nPeriode Validitas Hadiah yang Disesuaikan & Cara Memperpanjangnya:\nHadiah eksklusif yang disesuaikan akan tersedia di rak untuk masa berlaku 30 hari. Untuk memungkinkan pengguna yang berpengaruh dan menginspirasi untuk terus menikmati pengalaman baru ini dan menampilkan gaya pribadi mereka, pengguna yang memiliki hadiah khusus dapat memperpanjang waktu simpan semua hadiah khusus mereka selama 30 hari dengan mengisi ulang $500 pada bulan tertentu.", + "customizedGiftRules": "Aturan Hadiah yang Disesuaikan", + "rulesUpload": "Aturan & Unggah", + "monthly": "Bulanan", + "playLog": "Log Putar:", + "selectACountry": "Pilih negara:", + "message": "Pesan", + "clearCache": "Hapus Tembolok", + "customized": "Disesuaikan", + "searchInputHint": "Masukkan nomor rekening/kamar", + "kickRoomTips": "Anda telah diusir dari ruangan.", + "joinRoomTips": "bergabung ke ruangan!", + "roomSetting": "Pengaturan Ruangan", + "roomDetails": "Detail Kamar", + "systemRoomTips": "Harap menjaga kesopanan dan rasa hormat. Konten pornografi atau vulgar apa pun dilarang keras di Aslan. Akun apa pun yang ditemukan melanggar akan diblokir secara permanen. Kami dengan hormat meminta semua pengguna untuk secara sadar mematuhi pedoman komunitas Aslan.", + "copiedToClipboard": "Disalin ke papan klip", + "recharge": "Isi ulang", + "receivedFromALuckyGift": "Diterima dari hadiah keberuntungan.", + "followedYou": "Mengikutimu", + "agentCenter": "Pusat Agensi", + "areYouSureYouWantToClearLocalCache": "Apakah Anda yakin ingin menghapus cache lokal?", + "clearMessage": "Hapus pesan layar", + "report": "Laporan", + "coins3": "Koin", + "giftSpecialEffects": "Hadiahkan efek khusus", + "basicFeatures": "Fitur Dasar", + "task": "Tugas", + "importantReminder": "Pengingat Penting", + "entryVehicleAnimation": "Animasi kendaraan masuk (VIP3)", + "floatingAnimationInGlobal": "Animasi mengambang secara global", + "entryVehicleAnimation2": "Pengguna dengan hak istimewa VlP3 atau lebih tinggi dapat menggunakan fungsi ini untuk menonaktifkan animasi kendaraan.", + "dailyTasks": "Tugas Harian", + "enterRoomConfirmTips": "Apakah Anda yakin ingin memasuki ruangan?", + "followSucc": "Diikuti dengan sukses", + "goldListort": "Daftar Emas", + "rechargeList": "Daftar Isi Ulang", + "edit": "Sunting", + "swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt": "*Tips: Geser ke kiri pada area layar mengambang untuk menutupnya dengan cepat.", + "enterThisVoiceChatRoom": "Masuk ke ruang obrolan suara ini?", + "go": "Pergi", + "done": "Selesai", + "improvementTasks": "Tugas Peningkatan", + "save": "Menyimpan", + "kickPrevention": "Pencegahan tendangan", + "freeChatSpeak": "Obrolan & Bicara Gratis", + "vipExclusiveVehicles": "Kendaraan Eksklusif VIP", + "nickName": "Nama panggilan", + "buyVip": "Beli VIP", + "gender": "Jenis kelamin", + "unFollow": "Berhenti mengikuti", + "days": "hari", + "permanent": "Permanen", + "yourVipWillExpire": "Masa berlaku VIP Anda akan berakhir pada {1}", + "cantBuyVip": "Tidak dapat melanjutkan pembelian", + "country": "Negara", + "birthday": "Hari ulang tahun", + "man": "Pria", + "woman": "Wanita", + "apple": "Apel", + "google": "Google", + "idIcon": "Ikon ID", + "inviteToBecomeAHost": "Undang untuk menjadi tuan rumah", + "currentLevelPrivilegesAndCostumes": "Hak istimewa dan kostum level saat ini: {1}", + "uploadGifAvatar": "Unggah avatar GlF (VIP2)", + "uploadProfilePicture": "Unggah Gambar Profil", + "permissionSettings": "Pengaturan izin", + "vipMicSoundWave": "Gelombang Suara Mikrofon VIP", + "startVoiceParty": "Mulai pesta suara!", + "enterRoomTips": "{1} Masuk ke ruangan", + "roomName": "Nama Kamar", + "roomMember": "Anggota Kamar", + "pleaseSelectYourCountry": "Silakan pilih negara Anda.", + "pleaseSelectYourGender": "Silakan pilih jenis kelamin Anda.", + "pleaseEnterNickname": "Silakan masukkan nama panggilan.", + "countryRegion": "Negara & Wilayah", + "dateOfBirth": "Tanggal lahir", + "mute": "Bisu", + "exit": "KELUAR", + "mysteriousInvisibility": "Gaib yang misterius", + "antiBlock": "Anti-Blok", + "createFamilyForFree": "Buat Keluarga Gratis", + "privateChat": "Obrolan Pribadi", + "vipBirthdayGift": "Hadiah Ulang Tahun VIP", + "storeDiscount": "Diskon Toko Diskon {1}", + "membershipFreeChatSpeak": "Obrolan & Bicara bebas keanggotaan", + "priorityRoomSorting": "Penyortiran Kamar Prioritas", + "userColoredID": "ID Berwarna Pengguna", + "vipEmoticon": "Emotikon VIP({1})", + "pleaseSelectaItem": "Silakan pilih item", + "areYouRureRoRecharge": "Apakah Anda yakin untuk mengisi ulang?", + "mInimize": "Menyimpan", + "everyone": "Setiap orang", + "shop": "Toko", + "roomOwner": "Pemilik Kamar", + "dailyTaskRewardBonus": "Bonus Hadiah Tugas Harian ({1} XP)", + "userLevelXPBoost": "Peningkatan XP Tingkat Pengguna ({1} XP)", + "pleaseUpgradeYourVipLevel": "Silakan tingkatkan level VIP Anda.", + "andAboveUsers": "{1} Dan Pengguna Di Atas", + "privileges": "{1} Hak istimewa", + "basicPermissions": "Izin dasar", + "hostCenter": "Pusat Tuan Rumah", + "allOnMicrophone": "Semuanya menggunakan mikrofon", + "usersOnMicrophone": "Pengguna di mikrofon", + "allInTheRoom": "Semua di kamar", + "send": "Mengirim", + "crop": "Tanaman", + "goToUpgrade": "Pergi untuk meningkatkan", + "exclusiveEmojiWillBeReleasedAfterBecoming": "Emoji eksklusif akan dirilis setelah menjadi", + "preventBeingBlocked": "Mencegah Diblokir", + "enableRankIncognitoMode": "Aktifkan Mode Penyamaran Peringkat", + "avoidBeingKicked": "Hindari Ditendang", + "vipPrivilege": "Hak istimewa VIP", + "finish": "Menyelesaikan", + "takeTheMic": "Ambil mikrofonnya", + "openTheMic": "Buka mikrofon", + "muteTheMic": "Matikan mikrofon", + "unlockTheMic": "Buka kunci mikrofon", + "leavelTheMic": "Tinggalkan mikrofonnya", + "lockTheMic": "Kunci mikrofonnya", + "removeTheMic": "Hapus mikrofon", + "inviteToTheMicrophone": "Undang ke mikrofon", + "openUserProfleCard": "Buka kartu profil pengguna", + "obtain": "memperoleh", + "win2": "Menangkan {1}", + "backTheRoom": "Ruang belakang", + "toConsume": "Untuk mengkonsumsi", + "howToUpgrade": "Bagaimana cara meningkatkannya?", + "spendCoinsToGainExperiencePoints": "Habiskan Koin untuk mendapatkan poin pengalaman", + "higherLevelFancierAvatarFrame": "Tingkat yang lebih tinggi, lencana/bingkai avatar yang lebih menarik", + "medalAndAvatarFrameRewards": "Hadiah medali dan bingkai avatar", + "all": "Semua", + "gift": "Hadiah", + "chat": "Mengobrol", + "owner": "Pemilik", + "store": "Toko", + "admin": "Admin", + "member": "Anggota", + "guest": "Tamu", + "submit": "Kirim", + "claim": "Mengeklaim", + "complete": "Menyelesaikan", + "shareTo": "Bagikan ke", + "copyLink": "Salin Tautan", + "faceBook": "Facebook", + "whatsApp": "ada apa", + "snapChat": "Snapchat", + "taskNamePersonalGameConsume": "Pengeluaran Game", + "taskNameRoomNewMember": "Anggota Ruang Baru", + "taskNameRoomOwnerSendRedPacket": "Pemilik Kamar mengirimkan satu paket merah", + "taskNameRoomOwnerSendGiftUser": "Pemilik Kamar Mengirim Hadiah", + "taskNameRoomMicUser120Min": "Anggota dengan 120+ menit menggunakan mikrofon", + "taskNameRoomMicUser60Min": "Anggota dengan 60+ menit menggunakan mikrofon", + "taskNameRoomMicUser30Min": "Anggota dengan 30+ menit menggunakan mikrofon", + "taskNamePersonalSendGift": "Kirim Hadiah ke Pengguna", + "taskNameRoomOnlineUserCount": "Anggota ruang online", + "taskNameRoomOwnerInviteMic": "Undang anggota ke Mic", + "taskNameRoomUserSendGiftGold": "Koin yang diberikan oleh anggota", + "taskNameRoomOwnerSendGiftGold": "Koin yang diberikan oleh pemilik kamar", + "taskNamePersonalMagicGiftGold": "Koin Dimenangkan dengan Mengirim Hadiah Ajaib", + "taskNamePersonalLuckyGiftGold": "Koin Dimenangkan dengan Mengirim Hadiah Keberuntungan", + "taskNameRoomOwnerMicTime": "Pemilik Kamar menyalakan mikrofon di dalam kamar", + "taskNamePersonalActiveInRoom": "Jadilah Aktif di Ruangan Orang Lain", + "taskNamePersonalMicInRoom": "Ayo Mikrofon", + "dailyCoinBonanzaRulesDetail": "1. Tugas Pribadi Harian dan Tugas Pemilik Ruangan Harian dapat diselesaikan satu kali per pengguna per hari. Tugas diatur ulang pada 00:00:00 waktu Saudi.\n2. Tugas pribadi sehari-hari hanya dapat diselesaikan di ruangan orang lain; tugas pemilik ruangan hanya dapat diselesaikan di ruangan Anda sendiri.\n3. Tugas pemberian hadiah hanya menghitung hadiah yang dikirim di ruangan, bukan hadiah yang dikirim di feed.\n4. Jika beberapa akun dibuat menggunakan perangkat yang sama atau kartu SIM yang sama dengan cara apa pun, hadiah untuk semua tugas hanya dapat diklaim satu kali.", + "dailyCoinBonanzaRules": "Aturan Bonanza Koin Harian", + "roomOwnerTasks": "Tugas Pemilik Ruangan", + "personalTasks": "Tugas Pribadi", + "noPromptsToday": "Tidak ada petunjuk hari ini.", + "getPaidToRefer": "Dapatkan Bayaran untuk Merujuk", + "membershipFee": "Biaya Keanggotaan", + "membershipFeeTips1": "Silakan tentukan biaya keanggotaan untuk kamar Anda. Pengguna dapat bergabung dengan kamar Anda dengan membayar biayanya.", + "membershipFeeTips2": "Emas yang dibutuhkan pengguna untuk menjadi anggota ruangan. Pemilik ruangan akan mendapatkan 50% emas.", + "freePrice": "Biaya: 0-10.000", + "touristsSendText": "Turis mengirim SMS", + "touristsTakeToTheMic": "Wisatawan mendengarkan mikrofon", + "theMembershipFee": "Biaya keanggotaan", + "theModificationsMade": "Modifikasi yang dilakukan kali ini tidak akan disimpan setelah keluar", + "viewFrame": "Lihat Bingkai", + "enterRoomName": "Masukkan nama ruangan", + "headdress": "Bingkai", + "mountains": "Kendaraan", + "purchaseIsSuccessful": "Pembelian berhasil", + "buy": "Membeli", + "followed": "Diikuti", + "follow2": "Ikuti:{1}", + "fans2": "Penggemar:{1}", + "vistors2": "Pengunjung:{1}", + "personal2": "Pribadi:", + "family2": "Keluarga:", + "conntinue": "Lanjutkan", + "confirmBuyTips": "Apakah Anda yakin ingin membeli?", + "purchase": "Pembelian", + "setRoomPassword": "Tetapkan kata sandi ruangan", + "inputRoomPassword": "Masukkan kata sandi ruangan", + "enter": "Memasuki", + "createDynamicSuccess": "Berhasil membuat dinamis", + "deleteDynamicTips": "Apakah Anda yakin ingin menghapus dinamika ini?", + "deleteCommentTips": "Apakah Anda yakin ingin menghapus komentar ini?", + "deleteSuccessful": "Penghapusan berhasil!", + "itemsLeft": "Barang tersisa", + "password": "Kata sandi", + "replySucc": "Balasan berhasil", + "comment": "Komentar", + "showMore": "Tampilkan lebih banyak", + "showLess": "Tampilkan lebih sedikit", + "enterPassword": "Masukkan Kata Sandi", + "enterAccount": "Masuk ke Akun", + "logIn": "Masuk", + "saySomething": "Katakan sesuatu...", + "sayHi": "Ucapkan salam..", + "pleaseChatFfriendly": "Silakan ngobrol dengan ramah", + "unLockTheRoom": "Buka Kunci Ruangan", + "operationSuccessful": "Operasinya berhasil.", + "adminByHomeowner": "ditetapkan sebagai administrator oleh pemilik rumah.", + "memberByHomeowner": "ditetapkan sebagai anggota oleh pemilik rumah.", + "touristByHomeowner": "ditetapkan sebagai turis oleh pemilik rumah.", + "becomeHost": "Lamar Untuk Menjadi Tuan Rumah", + "superFans": "Penggemar super:", + "setUpAnIdentity": "Siapkan identitas", + "kickedOutOfRoom": "Ditendang keluar dari ruangan", + "playGiftMusicAndDynamicMusic": "Mainkan musik hadiah dan musik dinamis", + "knapsack": "ransel", + "bdLeader": "Pemimpin BD", + "picture": "Gambar", + "theImageSizeCannotExceed": "Gagal mengunggah: File harus berukuran kurang dari 2 MB.", + "activity": "Aktivitas", + "alreadyAnAdministrator": "Sudah menjadi administrator", + "alreadyAnMember": "Sudah menjadi anggota", + "alreadyAnTourist": "Sudah menjadi turis", + "touristsCannotSendMessages": "Wisatawan tidak dapat mengirim pesan", + "touristsAreNotAllowedToGoOnTheMic": "Wisatawan tidak diperbolehkan menggunakan mikrofon", + "lockTheRoom": "Kunci Ruangan", + "special": "Spesial", + "visitorList": "Daftar Pengunjung", + "successfulWear": "Pakaian yang sukses", + "confirmUnUseTips": "Apakah Anda mengonfirmasi untuk menghapusnya?", + "custom": "Kebiasaan", + "myItems": "Barang Saya", + "use": "Menggunakan", + "unUse": "Tidak digunakan", + "renewal": "Pembaruan", + "wallet": "Dompet", + "profile": "Profil", + "giftwall": "dinding hadiah", + "announcement": "Pengumuman", + "blockedList": "Daftar yang diblokir", + "country2": "Negara:", + "sendTo": "Kirim ke", + "medals": "Medali", + "activityMedal": "Medali Aktivitas", + "achievementMedal": "Medali Prestasi", + "credits": "Kredit: {1}", + "successfullyUnloaded": "Berhasil dibongkar", + "expired": "Kedaluwarsa", + "day": "Hari", + "inUse": "Sedang digunakan", + "confirmUseTips": "Apakah Anda ingin mengonfirmasi penggunaannya?", + "pleaseUploadUserAvatar": "Silakan unggah avatar.", + "joinMemberTips": "Jika Anda adalah pengunjung ruangan, Anda tidak dapat mengambil mikrofon.", + "giftGivingSuccessful": "Pemberian hadiah berhasil.", + "theAccountPasswordCannotBeEmpty": "Akun atau kata sandi tidak boleh kosong.", + "invitesYouToTheMicrophone": "{1} mengundang Anda ke mikrofon", + "english": "Bahasa inggris", + "chinese": "Cina", + "arabic": "Arab", + "darkMode": "Mode Gelap", + "lightMode": "Modus Cahaya", + "systemDefault": "Bawaan Sistem", + "pleaseGetOnTheMicFirst": "Silakan nyalakan mikrofonnya terlebih dahulu.", + "duration2": "Durasi:{1}" +} diff --git a/assets/l10n/intl_tr.json b/assets/l10n/intl_tr.json index 57912ae..46c163e 100644 --- a/assets/l10n/intl_tr.json +++ b/assets/l10n/intl_tr.json @@ -359,6 +359,14 @@ "get": "Al", "inRocket": "Rokette", "roomRocketHelpTips": "1. Odada hediye göndermek roket enerjisini artırır. *1 altın jetton hediyesi = 1 roket enerji puanı; şanslı hediyeler roket enerjisini hediyenin altın jetton değerinin %4'ü kadar artırır.\n2. Roket enerjisi tamamen dolduğunda, oda roketi fırlatabilir. Fırlatmadan sonra ödüller otomatik olarak dağıtılacaktır.\n3. Farklı roket seviyeleri farklı ödüller sunar.\n4. Roket fırlatıldığında, odadaki tüm kullanıcılar roket ödülünü talep edebilir.5. Roket enerjisi her gün 00:00'da sıfırlanır.", + "roomRocketRecordAction": "Kayıt", + "roomRocketRewardTop1": "Top1", + "roomRocketRewardSetOff": "Fırlatan", + "roomRocketRewardInRoom": "Odada", + "roomRocketResetCountdown": "Sıfırlamaya 23 saat, 59 dakika ve 59 saniye kaldı", + "roomRocketRecordRoom": "Oda", + "roomRocketRecordReward": "Ödül", + "roomRocketRecordLast35Days": "Yalnızca son 35 günün kayıtlarını göster", "couponRecord": "Kupon Kullanım Kaydı", "inRoom": "Odada", "searchCouponHint": "Kupon Ara", @@ -463,7 +471,7 @@ "bdCenter": "BD Merkezi", "renewVip": "VIP'yi Yenile", "rechargeAgency": "Yükleme Temsilciliği", - "adminCenter": "Yönetici Merkezi", + "adminCenter": "Çalışma Merkezi", "goldList": "Altın Listesi", "followList": "Takip Listesi", "fansList": "Hayran Listesi", @@ -765,4 +773,4 @@ "systemDefault": "Sistem Varsayılanı", "pleaseGetOnTheMicFirst": "Lütfen önce mikroya çıkın.", "duration2": "Süre:{1}" -} \ No newline at end of file +} diff --git a/assets/l10n/intl_ur.json b/assets/l10n/intl_ur.json new file mode 100644 index 0000000..8a95a42 --- /dev/null +++ b/assets/l10n/intl_ur.json @@ -0,0 +1,777 @@ +{ + "signInWithGoogle": "گوگل کے ساتھ سائن ان کریں۔", + "or": "یا", + "signInWithYourAccount": "اپنے اکاؤنٹ سے سائن ان کریں۔", + "signInWithApple": "ایپل کے ساتھ سائن ان کریں۔", + "loginRepresentsAgreementTo": "لاگ ان معاہدے کی نمائندگی کرتا ہے۔", + "termsofService": "سروس کی شرائط", + "privaceyPolicy": "رازداری کی پالیسی", + "tips": "تجاویز", + "dailyTasksTips": "نوٹ: کاموں سے نمٹنے کے لیے تیار ہیں؟ انہیں اپنے پسندیدہ کمروں میں تلاش کریں۔", + "searchNoDataTips": "وہ کمرہ یا صارف lD درج کریں جسے آپ تلاش کرنا چاہتے ہیں۔", + "youHaveNotHadVIPYet": "آپ نے ابھی تک VIP نہیں کیا ہے، آو اور اسے آزمائیں", + "games": "گیمز", + "vipAccelerating": "{1} تیز کرنا", + "mine": "میرا", + "luckGiftRuleTips": "ایک خوش قسمت تحفہ دیں اور 1000 گنا تک سونے کا سکہ انعام جیتیں! خوش قسمت تحائف حاصل کرنے والے صارفین درج ذیل فوائد سے لطف اندوز ہو سکتے ہیں۔ \n(1) خوش قسمت تحائف وصول کرنے والے صارفین کے لیے: تحفے کی قیمت کی بنیاد پر +4% دلکش تجربہ کی قیمت۔ 
(2) اگر وصول کنندہ میزبان ہے: تحفہ کی مالیت کی بنیاد پر +4% میزبان تنخواہ ہدف کی قیمت۔", + "badge": "بیج", + "vipRippleTheme": "وی آئی پی ریپل تھیم", + "glory": "جلال", + "badgeHonor": "بیج/گلوری", + "party": "پارٹی", + "other": "دیگر", + "gifProfileUpload": "GIF پروفائل اپ لوڈ", + "levelMedal": "لیول میڈل", + "levelIcon": "سطح کا آئیکن", + "maliciousHarassment": "بدنیتی پر مبنی ہراساں کرنا", + "event": "واقعہ", + "roomEdit": "کمرے میں ترمیم کریں۔", + "cancelRoomPassword": "کیا آپ واقعی کمرے کا پاس ورڈ حذف کرنا چاہتے ہیں؟", + "roomMemberFee": "کمرہ ممبر کی فیس", + "roomTheme2": "روم تھیم", + "blockedList2": "مسدود فہرست", + "roomPassword": "کمرے کا پاس ورڈ", + "numberOfMic": "مائیک کی تعداد", + "pleaseEnterContent": "براہ کرم مواد درج کریں۔", + "profilePhoto": "پروفائل فوٹو", + "aboutMe": "میرے بارے میں", + "myRoom": "میرا کمرہ", + "noHistoricalRecordsAvailable": "کوئی تاریخی ریکارڈ دستیاب نہیں ہے۔", + "inviteNewUsersToEarnCoins": "سکے حاصل کرنے کے لیے نئے صارفین کو مدعو کریں۔", + "crateMyRoom": "میرا کمرہ بنائیں", + "vipSpecialGiftTassel": "VIP خصوصی تحفہ tassel", + "vipEntranceEffect": "VIP داخلے کا اثر", + "casualInteraction": "آرام دہ اور پرسکون تعامل", + "haveGamePlayingTips": "آپ کے پاس ایک گیم جاری ہے۔ براہ کرم موجودہ گیم سے باہر نکلیں۔ کیا آپ واقعی باہر نکلنا چاہتے ہیں؟", + "historicalTour": "تاریخی دورہ", + "gameCenter": "کھیل مرکز", + "returnToVoiceChat": "صوتی چیٹ پر واپس جائیں؟", + "exitGameMode": "گیم موڈ سے باہر نکلیں۔", + "enterTheRoom": "کمرے میں داخل ہوں۔", + "invite": "دعوت دیں۔", + "inviteGoRoomTips": "ہمیشہ آپ کے لیے بارش ہو یا چمک۔ اندر داخل ہوں اور ہیلو کہو!", + "confirmInviteThisUserToTheRoom": "اس صارف (ID:{1}) کو کمرے میں مدعو کرنے کی تصدیق کریں؟", + "honor": "عزت", + "clearCacheSuccessfully": "کیشے کو کامیابی سے صاف کریں۔", + "sent": "بھیجا", + "keep": "رکھو", + "open": "کھولیں۔", + "deleteAccountTips2": "*اگر آپ اپنا خیال بدل لیتے ہیں، تو آپ سات دنوں میں اپنے موجودہ اکاؤنٹ میں دوبارہ لاگ ان کر سکتے ہیں، اور ہم خود بخود آپ کا اکاؤنٹ بحال کر دیں گے۔ اگر سات دنوں کے اندر کوئی بحالی نہیں ہوتی ہے، تو اکاؤنٹ مستقل طور پر حذف کر دیا جائے گا۔", + "deleteAccountTips": "آپ کو اس اکاؤنٹ کے مکمل انتظامی حقوق حاصل ہیں۔ اگر آپ اکاؤنٹ کو حذف کرنا چاہتے ہیں، تو براہ کرم اس آپریشن سے منسلک درج ذیل خطرات سے آگاہ رہیں:\n1.اکاؤنٹ کامیابی کے ساتھ حذف ہونے کے بعد، آپ اپنے موجودہ اکاؤنٹ میں لاگ ان نہیں ہو پائیں گے۔ اکاؤنٹ حذف کرنا ایک مستقل عمل ہے۔\n2. اکاؤنٹ کے کامیابی سے حذف ہونے کے بعد، آپ اکاؤنٹ کا کوئی ڈیٹا بازیافت نہیں کر سکیں گے۔ تمام معلومات (بشمول کمرے، دوست)، ورچوئل کرنسی، تحائف، اور ورچوئل آئٹمز مستقل طور پر حذف کر دی جائیں گی اور انہیں بحال نہیں کیا جا سکتا۔\n3. کولنگ آف پیریڈ: اگر آپ اکاؤنٹ کو بحال نہیں کرتے ہیں، تو آپ خریداری کے صفحہ، واپسی کے صفحہ، یا ایپ کے کسی دوسرے صفحات تک رسائی حاصل کرنے سے قاصر ہوں گے۔\n4. کولنگ آف پیریڈ کے دوران یا اکاؤنٹ کے حذف ہونے کے بعد، پروفائل صفحہ اس بات کی نشاندہی کرے گا کہ اسے حذف کر دیا گیا ہے۔ آپ کے اکاؤنٹ کو دوسروں کی طرف سے تلاش یا ان تک رسائی سے بچانے کے لیے، آپ کی ذاتی معلومات کو روزانہ کے افعال سے متعلق سسٹمز سے ہٹا دیا جائے گا۔ جب اکاؤنٹ کو حذف کرنے میں قومی سلامتی، دیوانی یا فوجداری کارروائی، یا فریق ثالث کے جائز حقوق اور مفادات کا تحفظ شامل ہو، تو اہلکار صارف کی اکاؤنٹ حذف کرنے کی درخواست کو مسترد کرنے کا حق محفوظ رکھتا ہے۔\nاگر آپ کو یقین ہے کہ آپ اپنے موجودہ اکاؤنٹ سے تمام ذاتی ڈیٹا حذف کرنا چاہتے ہیں، تو براہ کرم \"اکاؤنٹ حذف کریں\" پر کلک کریں۔", + "accountDeletionNotice": "اکاؤنٹ حذف کرنے کا نوٹس:", + "thisUserHasBeenBlacklisted": "اس صارف کو بلیک لسٹ کر دیا گیا ہے۔", + "trend": "رجحان", + "like": "پسند", + "more": "مزید", + "discard": "رد کر دیں۔", + "catchFirstComment": "پہلا تبصرہ دیکھیں", + "reply": "جواب دیں۔", + "posting": "پوسٹنگ", + "dynamic": "متحرک", + "multiple": "متعدد", + "successfullyAddedToTheDynamicBlacklist": "کامیابی کے ساتھ متحرک بلیک لسٹ میں شامل کیا گیا!", + "successfullyRemovedFromTheBlacklist": "بلیک لسٹ سے کامیابی کے ساتھ ہٹا دیا گیا!", + "successfullyRemovedFromTheDynamicBlacklist": "ڈائنامک بلیک لسٹ سے کامیابی کے ساتھ ہٹا دیا گیا!", + "successfullyAddedToTheBlacklist": "بلیک لسٹ میں کامیابی کے ساتھ شامل کیا گیا!", + "youAreCurrentlyCPRelationshipPleaseDissolve": "آپ فی الحال CP رشتہ میں ہیں۔\nبراہ کرم پہلے اسے تحلیل کریں۔", + "areYouSureToCancelBlacklist": "کیا آپ یقینی طور پر بلیک لسٹ کو منسوخ کرنا چاہتے ہیں؟", + "areYouSureYouWantToBlockThisUser": "کیا آپ واقعی اس صارف کو مسدود کرنا چاہتے ہیں؟", + "areYouSureYouWantToDynamicBlockThisUser": "کیا آپ کو متحرک بلیک لسٹ شامل کرنے کا یقین ہے؟", + "removeFromBlacklist": "بلیک لسٹ سے نکال دیں۔", + "moveToBlacklist": "بلیک لسٹ میں جائیں۔", + "userBlacklist": "یوزر بلیک لسٹ", + "specialEffectsManagement": "خصوصی اثرات کا انتظام", + "wishingYouHappinessEveryDay": "آپ کو ہر دن خوشی کی خواہش کرتا ہوں۔", + "newMessage": "نیا پیغام", + "createRoomSuccsess": "کمرہ کامیابی بنائیں!", + "contactUs": "مجھ سے رابطہ کریں۔", + "systemAnnouncementTips1": "دھوکہ دہی سے بچاؤ:", + "systemAnnouncementTips": "معلومات کی تصدیق صرف سرکاری چینلز کے ذریعے کریں۔ کبھی بھی تھرڈ پارٹی سافٹ ویئر ڈاؤن لوڈ نہ کریں، ذاتی ڈیٹا کا اشتراک نہ کریں، یا بیرونی درخواستوں کی بنیاد پر رقم کی منتقلی نہ کریں۔ آفیشل اسٹاف آئی ڈیز صرف 10000، 10003، اور 10086 ہیں۔ کسی بھی شبہ کی صورت میں، روکیں اور بذریعہ رپورٹ کریں", + "systemAnnouncement": "سسٹم کا اعلان", + "doNotClickUnfamiliarTips": "غیر مانوس لنکس پر کلک نہ کریں، کیونکہ وہ آپ کی ذاتی معلومات کو بے نقاب کر سکتے ہیں۔ اپنے شناختی کارڈ یا بینک کارڈ کی تفصیلات کبھی بھی کسی کے ساتھ شیئر نہ کریں۔", + "atTag": "@Tag", + "sayHi2": "ہیلو کہو", + "canSendMsgTips": "دونوں فریقوں کو نجی پیغامات بھیجنے سے پہلے ایک دوسرے کی پیروی کرنے کی ضرورت ہے۔", + "msgSendRedEnvelopeTips": "*سرخ لفافوں پر 10% سروس فیس وصول کی جائے گی، اور وصول کنندگان کو سرخ لفافے کی قیمت کا صرف 90% وصول کیا جائے گا۔ بھیجنے والے کی دولت کی سطح لیول 10 سے زیادہ ہونی چاہیے۔", + "leavFamilyTips": "کیا آپ واقعی اپنا موجودہ قبیلہ چھوڑنا چاہتے ہیں؟ قبیلے میں دوبارہ شامل ہونے کے لیے آپ کو 1 دن انتظار کرنا پڑے گا۔", + "leavingTheFamily": "خاندان کو چھوڑ کر", + "familyNotifcations": "خاندانی اطلاعات", + "familyNews": "خاندانی خبریں۔", + "reapply": "دوبارہ درخواست دیں۔", + "cancelRequestFamilyMsg": "فیملی 【{1} کی فیملی】 میں شامل ہونے کی آپ کی درخواست کا جائزہ لیا جا رہا ہے، کیا آپ واقعی درخواست کو منسوخ کرنا چاہتے ہیں؟", + "cancelRequest": "درخواست منسوخ کریں۔", + "pending": "زیر التواء", + "familyAnnouncement": "خاندانی اعلان", + "enterFamilyAnnouncement": "براہ کرم خاندانی اعلان درج کریں۔", + "disbandTheFamily": "خاندان کو توڑ دو", + "editFamily": "فیملی میں ترمیم کریں۔", + "supporter": "حمایتی", + "rechargeSuccessful": "ریچارج کامیاب", + "transactionReceived": "لین دین موصول ہوا۔", + "createFamilySuccess": "خاندانی کامیابی بنائیں۔", + "numberOfSign": "نشان کی تعداد: {1}", + "hostWeeklyRank": "میزبان ہفتہ وار رینک", + "supporterWeeklyRank": "سپورٹر ہفتہ وار رینک", + "memberList": "ممبر لسٹ", + "treasureChest": "خزانہ سینے", + "xxfamily": "{1} کا خاندان", + "applicationRecord": "درخواست کا ریکارڈ", + "createFamily": "فیملی بنائیں", + "familyName": "خاندانی نام", + "createAFamily": "ایک خاندان بنائیں", + "searchFamilyIdHint": "براہ کرم خاندان کے مالک کی شناخت درج کریں۔", + "enterFamilyInfo": "برائے مہربانی اپنے خاندان کا مختصر تعارف کروائیں!", + "enterFamilyName": "براہ کرم اپنا خاندانی نام درج کریں۔", + "familyInfo": "خاندانی تعارف", + "joinFamily": "ایک خاندان میں شامل ہوں۔", + "appUpdateTip": "ایپ کا ایک نیا ورژن ہے ({1})، براہ کرم جائیں اور اسے ڈاؤن لوڈ کریں؟", + "ownerIncomeCoins": "مالک کی آمدنی:{1} سکے", + "game": "کھیل", + "skip2": "چھوڑیں۔", + "coins4": "سکے", + "currentVip": "موجودہ وی ​​آئی پی", + "weekStart": "ہفتہ کا آغاز", + "forMoreRewardsPleaseCheckTheTaskCenter": "مزید انعامات کے لیے، براہ کرم ٹاسک سینٹر کو چیک کریں۔", + "kingQuuen": "کنگ کوئن", + "ramadan": "رمضان", + "updateNow": "ابھی اپ ڈیٹ کریں۔", + "allGames": "تمام گیمز", + "fishClass": "فش کلاس", + "greedyClass": "لالچی طبقہ", + "raceSeries": "ریس سیریز", + "slotsClass": "سلاٹس کلاس", + "others": "دوسرے", + "hotGames": "گرم، شہوت انگیز کھیل", + "chatBox": "چیٹ باکس", + "termsOfServicePrivacyPolicyTips": "جاری رکھ کر آپ سروس کی شرائط اور رازداری کی پالیسی سے اتفاق کرتے ہیں۔", + "and": "اور", + "pleaseSelectTheTypeContent": "براہ کرم توہین آمیز مواد کی قسم منتخب کریں۔", + "wearHonor": "عزت پہن لو", + "illegalInformation": "غیر قانونی معلومات", + "inappropriateContent": "نامناسب مواد", + "personalAttack": "ذاتی حملہ", + "confirm": "تصدیق کریں۔", + "spam": "سپیم", + "countdownMinutes": "الٹی گنتی منٹ:", + "number2": "نمبر:", + "fraud": "فراڈ", + "received": "موصول ہوا۔", + "currentProgress": "موجودہ پیشرفت", + "currentStage": "موجودہ مرحلہ:{1}", + "roomReward2": "کمرے کا انعام:{1}", + "roomReward": "کمرہ انعام", + "expirationTime": "میعاد ختم ہونے کا وقت", + "ownerSendTheRedEnvelope": "مالک انعامی سکے بھیجیں۔", + "rewardCoins": "انعامی سکے:{1} سکے", + "lastWeekProgress": "پچھلے ہفتے کی پیشرفت", + "redEnvelopeTips2": "*اگر وقت کی حد کے اندر سرخ بیگ کا دعویٰ نہیں کیا جاتا ہے، تو باقی سکے اس صارف کو واپس کردیئے جائیں گے جس نے سرخ بیگ بھیجا تھا۔", + "goToRecharge": "ریچارج پر جائیں۔", + "deleteAccount2": "اکاؤنٹ حذف کریں({1}s)", + "areYouSureYouWantToDeleteYourAccount": "کیا آپ واقعی اپنا اکاؤنٹ حذف کرنا چاہتے ہیں؟", + "insufhcientGoldsGoToRecharge": "ناکافی سونا، ابھی ریچارج کریں!", + "coins2": "{1}سکے", + "remainingNumberTips": "دستیاب کی بقیہ تعداد:({1}/{2})", + "collectionTimeTips": "جمع کرنے کا وقت:{1}({2}/{3})", + "sendARedEnvelope": "ایک سرخ لفافہ بھیجیں۔", + "sendRedPackConfirmTips": "کیا آپ واقعی سرخ پیکٹ بھیجنا چاہتے ہیں؟", + "redEnvelopeSendingRecords": "سرخ لفافہ بھیجنے کا ریکارڈ:", + "redEnvelope": "سرخ لفافہ", + "redEnvelopeRecTips2": "سرخ لفافوں کا دعویٰ کیا گیا ہے۔", + "redEnvelopeRecTips3": "سرخ لفافے جمع کرنے کا وقت ختم ہو گیا ہے!", + "openTheTreasureChest": "ایک سرخ بیگ باہر بھیجا!", + "redEnvelopeRecTips1": "کمائے گئے سکے آپ کے بٹوے میں جمع کر دیے گئے ہیں۔", + "redEnvelopeTips1": "سکے:", + "roomTools": "کمرے کے اوزار:", + "entertainment": "تفریح:", + "reportSucc": "رپورٹ کامیاب", + "pornography": "فحش نگاری", + "reportInputTips": "براہ کرم مسئلہ کو زیادہ سے زیادہ تفصیل سے بیان کریں تاکہ ہم اسے سمجھ سکیں اور حل کر سکیں۔", + "cancel": "منسوخ کریں۔", + "join": "شمولیت", + "items": "اشیاء", + "vistors": "Vistors", + "fans": "پرستار", + "balanceNotEnough": "سونے کے سکے کا ناکافی بیلنس۔ کیا آپ ٹاپ اپ پر جانا چاہتے ہیں؟", + "skip": "چھوڑیں {1}", + "wearMedal": "میڈل پہنیں۔", + "activityHonor": "سرگرمی کا اعزاز", + "achievementHonor": "کامیابی کا اعزاز", + "youDontHaveAnyHonorYet": "آپ کے پاس ابھی تک کوئی عزت نہیں ہے۔", + "letGoToWatch": "آئیے دیکھنے چلتے ہیں!", + "launchedARocket": "ایک راکٹ لانچ کیا", + "sendUserId": "UserID بھیجیں:{1}", + "giveUpIdentity": "شناخت چھوڑ دو", + "leaveRoomIdentityTips": "کیا آپ واقعی کمرے کی شناخت ترک کرنا چاہتے ہیں؟", + "joinMemberTips2": "کیا آپ ایک رکن کے طور پر کمرے میں شامل ہونے کی تصدیق کرنا چاہتے ہیں؟", + "sureUnfollowThisRoom": "اس کمرے کی پیروی ختم کرنا یقینی ہے؟", + "welcomeMessage": "ہماری درخواست میں خوش آمدید، {name}!", + "settings": "ترتیبات", + "account": "اکاؤنٹ", + "common": "عام", + "delete": "حذف کریں۔", + "copy": "کاپی", + "bio": "بایو", + "useCoupontips": "کیا آپ واقعی کوپن استعمال کرنا چاہتے ہیں؟", + "searchUserId": "صارف کی شناخت تلاش کریں۔", + "sendUser": "صارف بھیجیں۔", + "hobby": "شوق", + "sendCoupontips": "کیا آپ واقعی یہ کوپن اس صارف کو بھیجنا چاہتے ہیں؟", + "youDontHaveAnyCouponsYet": "آپ کے پاس ابھی تک کوئی کوپن نہیں ہے۔", + "recall": "یاد کرنا", + "youHaventFollowed": "آپ نے کسی کمرے کی پیروی نہیں کی ہے۔", + "deleteFromMyDevice": "میرے آلے سے حذف کریں۔", + "deleteOnAllDevices": "تمام آلات پر حذف کریں۔", + "messageHasBeenRecalled": "یہ پیغام واپس بلا لیا گیا ہے۔", + "recallThisMessage": "یہ پیغام یاد ہے؟", + "language": "زبان", + "feedback": "تاثرات", + "signedin": "سائن ان", + "receiveSucc": "کامیابی سے دعویٰ کیا گیا۔", + "about": "کے بارے میں", + "aboutUs": "ہمارے بارے میں", + "theme": "تھیم", + "wealthLevel": "دولت کی سطح", + "userLevel": "صارف کی سطح", + "goToUpload": "اپ لوڈ پر جائیں۔", + "logout": "لاگ آؤٹ کریں۔", + "luck": "قسمت", + "level": "سطح", + "vip": "وی آئی پی", + "vip1": "VIP1", + "vip2": "VIP2", + "vip3": "VIP3", + "vip4": "VIP4", + "vip5": "VIP5", + "vip6": "VIP6", + "themeGoToUploadTips": "1. اپ لوڈ کامیاب ہونے کے بعد 24 گھنٹے کے اندر جائزہ لیں۔\n2. جائزہ ناکام ہونے پر تمام سکے واپس کر دیے جائیں گے۔", + "home": "گھر", + "explore": "دریافت کریں۔", + "me": "مجھے", + "socialPrivilege": "سماجی استحقاق", + "information": "معلومات", + "myPhoto": "میری تصویر", + "cpRequest": "سی پی کی درخواست", + "areYouSureYouWantToSpend3": "*اگر دوسرا فریق CP کی دعوت کو مسترد کرتا ہے، تو آپ کے سکے آپ کے بٹوے میں واپس کردیئے جائیں گے۔", + "areYouSureYouWantToSpend": "کیا آپ واقعی خرچ کرنا چاہتے ہیں؟", + "areYouSureYouWantToSpend2": "اس صارف کو CP دعوت نامہ بھیجنا ہے؟", + "cpSexTips": "ایک ہی جنس کے جوڑے نہیں بن سکتے۔", + "underReview": "زیر جائزہ", + "doYouWantToDeleteIt": "کیا آپ اسے حذف کرنا چاہتے ہیں؟", + "chooseFromAblum": "ابلم میں سے انتخاب کریں۔", + "spaceBackground": "خلائی پس منظر", + "editProfile": "پروفائل میں ترمیم کریں۔", + "sendTheCpRequest": "سی پی کی درخواست بھیجیں۔", + "addCp": "CP شامل کریں۔", + "partWays": "حصے کے طریقے", + "reconcile": "صلح کرنا", + "separated": "الگ", + "areYouSureYouWantToSpend5": "{1} نے آپ سے احساس کا اقرار کیا۔ اگر آپ قبول کرتے ہیں تو آپ جوڑے بن جائیں گے۔", + "areYouSureYouWantToSpend6": "{1} آپ کے ساتھ واپس جانا چاہتا ہے۔ اگر آپ صلح کرنے کا فیصلہ کرتے ہیں، تو آپ کا تمام سابقہ ​​ڈیٹا بحال ہو جائے گا۔", + "reconcileInvitationTips": "*اگر دوسرا فریق CP کی دعوت کو مسترد کرتا ہے، تو آپ کے سکے آپ کے بٹوے میں واپس کردیئے جائیں گے۔", + "reconcileInvitation": "دعوت نامہ میں صلح کریں۔", + "areYouSureYouWantToSpend4": "اس صارف کو مفاہمت کا دعوت نامہ بھیجنا ہے؟", + "partWaysTips": "*اگر ایک جوڑے میں سے ایک پارٹنر الگ ہونے کا انتخاب کرتا ہے، تو 7 دن کی کولنگ آف مدت ہوگی۔ اس وقت کے دوران، دونوں فریقین مصالحت کا انتخاب کر سکتے ہیں، اور تمام ڈیٹا کو مفاہمت پر بحال کر دیا جائے گا۔ اگر 7 دن کی مدت کے اختتام تک مصالحت کا انتخاب نہیں کیا جاتا ہے، تو جوڑے کا ڈیٹا صاف کر دیا جائے گا۔", + "areYouSureYouWantToPartWaysWithYourCP": "کیا آپ واقعی اپنے CP سے الگ ہونا چاہتے ہیں؟", + "timeSpentTogether": "ایک ساتھ گزارا وقت: {1} دن", + "firstDay": "پہلا دن:{1}", + "numberOfMyCPs": "میرے CPs کی تعداد:({1}/{2})", + "props": "سہارے", + "medal": "میڈل", + "win": "جیت", + "dice": "ڈائس", + "rps": "آر پی ایس", + "areYouSureToCancelDynamicBlacklist": "کیا آپ یقینی طور پر متحرک بلیک لسٹ کو منسوخ کرنا چاہتے ہیں؟", + "blockUserDynamic": "یوزر ڈائنامک کو بلاک کریں۔", + "unblockUserDynamic": "یوزر ڈائنامک کو غیر مسدود کریں۔", + "operationFail": "آپریشن ناکام رہا۔", + "likedYourComment": "آپ کا تبصرہ پسند آیا۔", + "likedYourDynamic": "آپ کا Dynamic پسند آیا۔", + "doYouWantToKeepTheDraft": "کیا آپ مسودہ رکھنا چاہتے ہیں؟", + "cantSendDynamicTips": "آپ کو فی الحال ڈائنامکس پوسٹ کرنے سے روک دیا گیا ہے۔", + "operationsAreTooFrequent": "آپریشنز بہت زیادہ ہوتے ہیں۔", + "luckNumber": "لک نمبر", + "relationShip": "رشتہ", + "couple": "جوڑا {1}:", + "couple2": "جوڑے", + "reject": "رد کرنا", + "cpList": "سی پی کی فہرست", + "sound2": "آواز", + "hot": "گرم", + "selectCountry": "ملک منتخب کریں۔", + "giftEffect": "گفٹ اثر", + "winFloat": "ون فلوٹ", + "giftVibration": "لکی گفٹ ایفیکٹس", + "accept": "قبول کریں۔", + "noMatchedCP": "کوئی مماثل CP نہیں ہے۔", + "inviteYouToBecomeBD": "آپ کو بی ڈی بننے کی دعوت دیں۔", + "adminInviteRechargeAgent": "آپ کو ریچارج ایجنٹ بننے کے لیے مدعو کریں۔", + "confirmAcceptTheInvitation": "دعوت قبول کرنے کی تصدیق کریں؟", + "confirmDeclineTheInvitation": "دعوت نامے کے انکار کی تصدیق کریں؟", + "host": "میزبان", + "following": "پیروی کرنا", + "agent": "ایجنسی", + "approved": "منظور شدہ", + "agreeJoinFamilyTips": "کیا آپ اس صارف کو فیملی میں شامل ہونے کی اجازت دیتے ہیں؟", + "refuseJoinFamilyTips": "کیا آپ کو صارف کو خاندان میں شامل ہونے سے انکار کر دینا چاہیے؟", + "onlineUsers": "آن لائن صارفین({1}/{2}):", + "applyToJoin": "شامل ہونے کے لیے درخواست دیں۔", + "supporterList": "حامیوں کی فہرست", + "hostList": "میزبان کی فہرست", + "upToAdmins": "{1} ایڈمنز تک", + "upToMembers": "{1} اراکین تک", + "levelPrivileges": "سطحی مراعات", + "familyLevel": "خاندانی سطح", + "kickOutOfFamily": "خاندان سے نکال دو", + "disbandTheFamilyTips": "کیا آپ واقعی خاندان کو منقطع کرنا چاہتے ہیں؟", + "setAsFamilyAdmin": "فیملی ایڈمن کے طور پر سیٹ کریں۔", + "cancelFamilyAdmin": "فیملی ایڈمن کینسل کریں۔", + "kickFamilyUserTips": "کیا آپ واقعی اس صارف کو خاندان سے نکالنا چاہتے ہیں؟", + "familyMember2": "فیملی ممبر({1}/{2}):", + "familyMember3": "فیملی ممبر({1}):", + "familyAdmin2": "فیملی ایڈمن({1}/{2}):", + "familyOwner2": "خاندان کا مالک:", + "ra": "RA", + "roomAnnouncement": "کمرے کا اعلان", + "family3": "{1} کا خاندان", + "help": "مدد", + "rejected": "مسترد", + "boxContributeTips": "سرمایہ کاری آج ہو چکی ہے، براہ کرم دوبارہ سرمایہ کاری نہ کریں۔", + "familyHelpTips": "(1) اگر آپ 50 سکوں کی سرمایہ کاری کرتے ہیں، تو 5 افراد کی سرمایہ کاری کرنے پر پہلا ٹریژر چیسٹ کھل جائے گا۔ آپ 50 سکے وصول کرنے کے لیے کلک کر سکتے ہیں۔ آپ 50 سکے وصول کر سکتے ہیں۔\n(3) جب 20 لوگ سرمایہ کاری کریں گے تو دوسرا خزانہ سینے کو کھول دیا جائے گا۔ آپ 100 سکے وصول کر سکتے ہیں۔\n(4) اگر ٹریژر چیسٹ کا دعویٰ کرنے کے لیے صارفین کی مطلوبہ تعداد اسی دن نہیں پہنچتی ہے، تو ٹریژر چیسٹ کی پیشرفت اگلے دن دوبارہ ترتیب دی جائے گی۔\n(5) خزانے کے سینے کی پیشرفت اگلے دن دوبارہ ترتیب دی جائے گی۔ وہ صارفین جنہوں نے ابھی تک اسی دن اپنے خزانے کا دعویٰ نہیں کیا ہے انہیں وقت پر ان کا دعویٰ کرنا چاہیے۔\n(6)ٹریزر چیسٹ ری سیٹ کا وقت: 00:00 سعودی وقت۔", + "bd": "بی ڈی", + "coupon": "کوپن", + "search": "تلاش کریں۔", + "get": "حاصل کریں۔", + "inRocket": "راکٹ میں", + "roomRocketHelpTips": "1. کمرے میں تحائف بھیجنے سے راکٹ توانائی میں اضافہ ہوتا ہے۔ *1 سونے کا سکہ تحفہ = 1 راکٹ انرجی پوائنٹ؛ خوش قسمت تحائف تحفے کے سونے کے سکوں کی قیمت کا 4% راکٹ توانائی میں اضافہ کرتے ہیں۔\n2. راکٹ کی توانائی مکمل طور پر چارج ہونے کے بعد، کمرہ راکٹ کو لانچ کر سکتا ہے۔ لانچ کے بعد انعامات خود بخود تقسیم ہو جائیں گے۔\n3. راکٹ کی مختلف سطحیں مختلف انعامات پیش کرتی ہیں۔\n4. جب راکٹ لانچ ہوتا ہے، کمرے میں موجود تمام صارفین راکٹ کے انعام کا دعوی کر سکتے ہیں۔5۔ راکٹ توانائی ہر روز 00:00 پر دوبارہ ترتیب دی جاتی ہے۔", + "roomRocketRecordAction": "ریکارڈ", + "roomRocketRewardTop1": "Top1", + "roomRocketRewardSetOff": "لانچ", + "roomRocketRewardInRoom": "کمرے میں", + "roomRocketResetCountdown": "ری سیٹ ہونے میں 23 گھنٹے، 59 منٹ اور 59 سیکنڈ باقی", + "roomRocketRecordRoom": "کمرہ", + "roomRocketRecordReward": "انعام", + "roomRocketRecordLast35Days": "صرف گزشتہ 35 دنوں کے ریکارڈ دکھائیں", + "couponRecord": "کوپن کے استعمال کا ریکارڈ", + "inRoom": "کمرے میں", + "searchCouponHint": "کوپن تلاش کریں۔", + "giftCounter": "گفٹ کاؤنٹر", + "bDLeaderInviteYouToBecomeBDLeader": "آپ کو BDLeader بننے کی دعوت دیں۔", + "wins": "جیتتا ہے", + "inviteYouToBecomeHost": "آپ کو میزبان بننے کی دعوت دیں۔", + "friends": "دوستو", + "deleteConversationTips": "کیا آپ واقعی اس صارف کے ساتھ چیٹ کی سرگزشت کو حذف کرنا چاہتے ہیں؟", + "propMessagePrompt": "پروپ میسج پرامپٹ", + "inputUserId": "یوزر آئی ڈی درج کریں۔", + "fromLuckyGifts": "خوش قسمت تحائف سے", + "receive": "وصول کریں۔", + "checkInSuccessful": "چیک ان کامیاب", + "sginTips": "آپ کو ہر روز لاگ ان کرنے کے پہلے وقت پر انعام ملے گا۔ اگر آپ اپنے لاگ ان میں خلل ڈالتے ہیں، تو آپ کے دوبارہ لاگ ان ہونے پر انعام کا حساب پہلے دن سے لگایا جائے گا۔", + "vipBadge": "VIP بیج", + "vipProfileFrame": "VIP پروفائل فریم", + "vipProfileCard": "VIP پروفائل کارڈ", + "popular": "مقبول", + "recommend": "تجویز کریں۔", + "follow": "پیروی کریں۔", + "history": "تاریخ", + "hotRooms": "گرم کمرے", + "viewMore": "مزید دیکھیں", + "noData": "کوئی ڈیٹا نہیں۔", + "users": "صارفین", + "rooms": "کمرے", + "coins": "سکے", + "unread": "ان پڑھ", + "read": "پڑھیں", + "image": "[تصویر]", + "video": "[ویڈیو]", + "sound": "[آواز]", + "gift2": "[تحفہ]", + "clickHereToStartChatting": "کچھ بولو.....", + "receivedAMessage": "[ایک پیغام موصول ہوا]", + "confirmSwitchMicModelTips": "کیا آپ سیٹنگ موڈ میں سوئچ کی تصدیق کرتے ہیں؟", + "number": "نمبر", + "album": "البم", + "camera": "کیمرہ", + "system": "سسٹم", + "notifcation": "نوٹس", + "inviteYouToBecomeAgent": "آپ کو ایجنسی بننے کے لیے مدعو کریں۔", + "myMusic": "میری موسیقی", + "add": "شامل کریں۔", + "pullToLoadMore": "مزید لوڈ کرنے کے لیے کھینچیں۔", + "loadingFailedClickToRetry": "لوڈنگ ناکام، دوبارہ کوشش کرنے کے لیے کلک کریں۔", + "releaseToLoadMore": "مزید لوڈ کرنے کے لیے ریلیز کریں۔", + "haveMyLimits": "---میری حدود ہیں۔---", + "music": "موسیقی", + "free": "مفت", + "charm": "تحفہ کی توجہ", + "start": "شروع کریں۔", + "vipUseThisThemeTips": "صرف VlP لیول پر پورا اترنے والے اس تھیم کو استعمال کر سکتے ہیں۔", + "stop": "رک جاؤ", + "chats": "چیٹس", + "family": "خاندان", + "refuse": "انکار", + "agree": "متفق", + "thisFeatureIsCurrentlyUnavailable": "یہ فیچر فی الحال دستیاب نہیں ہے۔", + "pleaseSelectTheRecipient": "براہ کرم وصول کنندہ کو منتخب کریں۔", + "searchMemberIdHint": "براہ کرم ممبر کی شناخت درج کریں۔", + "unclaimedRedEnvelopes": "غیر دعویدار سرخ لفافے 24 گھنٹوں میں واپس کیے جاتے ہیں۔", + "redEnvelopeNotYetClaimed": "سرخ لفافے کا ابھی تک دعویٰ نہیں کیا گیا۔", + "redEnvelopeAmount": "سرخ لفافے کی رقم: {1} سکے", + "sentARedEnvelope": "ایک سرخ لفافہ بھیجا۔", + "theRedEnvelopeHasExpired": "سرخ لفافے کی میعاد ختم ہو چکی ہے۔", + "joinRequest": "درخواست میں شامل ہوں۔", + "welcomeToMyFamily": "میرے خاندان میں خوش آمدید!", + "scrollToTheBottom": "نیچے تک سکرول کریں۔", + "gameRules": "کھیل کے قوانین:", + "charmGameRulesTips": "مائیک پر تمام صارفین کے موصولہ گفٹ کوائن، 1 سکہ = 1 سکور (لکی گفٹ 1 کوائن = 0.04 سکور) دکھانے کے لیے ڈیش بورڈ کو آن کریں۔", + "inputYourOldPassword": "اپنا پرانا پاس ورڈ درج کریں۔", + "enterYourOldPassword": "اپنا پرانا پاس ورڈ درج کریں۔", + "setYourPassword": "اپنا پاس ورڈ سیٹ کریں۔", + "enterYourNewPassword": "اپنا نیا پاس ورڈ درج کریں۔", + "confirmYourPassword": "اپنے پاس ورڈ کی تصدیق کریں۔", + "theTwoPasswordsDoNotMatch": "دونوں پاس ورڈ مماثل نہیں ہیں۔", + "resetLoginPasswordtTips2": "پاس ورڈ 8-16 حروف لمبا ہونا چاہیے اور بڑے اور چھوٹے انگریزی حروف اور اعداد کا مجموعہ ہونا چاہیے (صرف نمبر نہیں)", + "resetLoginPassword": "لاگ ان پاس ورڈ کو دوبارہ ترتیب دیں۔", + "resetLoginPasswordtTips1": "اپنی یوزر آئی ڈی یا وینٹی آئی ڈی کے ساتھ لاگ ان کریں۔ اسلان اکاؤنٹ سے لاگ ان کرنا زیادہ محفوظ ہے۔", + "localMusic": "مقامی موسیقی", + "setLoginPassword": "لاگ ان پاس ورڈ سیٹ کریں۔", + "confirmSwitchMicThemeTips": "کیا آپ سیٹ سٹائل کے سوئچ کی تصدیق کرتے ہیں؟", + "pleaseUpgradeYourVipLevelFirst": "براہ کرم پہلے اپنا VIP لیول اپ گریڈ کریں۔", + "micTheme": "مائک تھیم", + "classicMic": "کلاسک {1} مائیک", + "yesterday": "کل {1}", + "monday": "پیر {1}", + "tuesday": "منگل {1}", + "wednesday": "بدھ {1}", + "thursday": "جمعرات {1}", + "friday": "جمعہ {1}", + "saturday": "ہفتہ {1}", + "sunday": "اتوار {1}", + "acceptedYour": "{1} نے آپ کا قبول کیا۔", + "youAccepted": "آپ نے {1} کو قبول کیا", + "openRedPackDialogTip": "سرخ لفافہ", + "micManagement": "مائیک مینجمنٹ", + "vipChatBox": "وی آئی پی چیٹ باکس", + "vipRoomCoverBorder": "وی آئی پی روم کور بارڈر", + "bdCenter": "بی ڈی سینٹر", + "renewVip": "VIP کی تجدید کریں۔", + "rechargeAgency": "ریچارج ایجنسی", + "adminCenter": "ورک سینٹر", + "goldList": "گولڈ لسٹ", + "followList": "فالو لسٹ", + "fansList": "شائقین کی فہرست", + "daily": "روزانہ", + "cp": "سی پی", + "female": "خاتون", + "male": "مرد", + "identity": "شناخت", + "adjust": "ایڈجسٹ کریں۔", + "warning": "وارننگ", + "screenshotTips": "اسکرین شاٹ (3 تک)", + "roomNotice": "کمرے کا نوٹس", + "roomTheme": "کمرے کی تھیم", + "description": "تفصیل:", + "inputDesHint": "براہ کرم مسئلہ کو زیادہ سے زیادہ تفصیل سے بیان کریں تاکہ ہم اسے سمجھ سکیں اور حل کر سکیں۔", + "roomProfilePicture": "کمرے کی پروفائل تصویر", + "userProfilePicture": "صارف کی پروفائل تصویر", + "userName": "صارف کا نام", + "pleaseSelectTheTypeToProcess": "براہ کرم عمل کرنے کے لیے قسم کا انتخاب کریں:", + "roomEditing": "روم ایڈیٹنگ", + "setAccount": "اکاؤنٹ سیٹ کریں۔", + "userEditing": "یوزر ایڈیٹنگ", + "enterTheUserId": "صارف کی شناخت درج کریں۔", + "enterTheRoomId": "روم آئی ڈی درج کریں۔", + "deleteAccount": "اکاؤنٹ حذف کریں۔", + "becomeAgent": "ایجنٹ بنیں۔", + "enterNickname": "عرفی نام درج کریں۔", + "selectYourCountry": "اپنا ملک منتخب کریں۔", + "inviteCode": "کوڈ کو مدعو کریں۔", + "magic": "جادو", + "list": "فہرست", + "walletBalance": "والیٹ بیلنس:", + "canOnlyBeShown": "*صرف آپ کے علاقے کے تمام صارفین کو دکھایا جا سکتا ہے۔", + "sendMessage": "پیغام بھیجیں۔", + "availableCountries": "دستیاب ممالک:", + "currentBalance": "موجودہ بیلنس:", + "successfulTransaction": "{1} کامیاب لین دین", + "hasBeenACoinAgencyForDays": "{1} دنوں سے سکے کی ایجنسی ہے۔", + "luckGiftSpecialEffects": "خوش قسمت تحفہ حرکت پذیری کے اثرات", + "theVideoSizeCannotExceed": "ویڈیو کا سائز 50M سے زیادہ نہیں ہو سکتا", + "weekly": "ہفتہ وار", + "rechargeRecords": "ریچارج ریکارڈز", + "balance": "توازن:", + "appStore": "ایپ اسٹور", + "searchCountry": "ملک تلاش کریں...", + "googlePay": "گوگل پے", + "biggestDiscount": "سب سے بڑی رعایت", + "officialRechargeAgent": "سرکاری ریچارج ایجنٹ", + "customizedGiftRulesContent": "اپنی مرضی کے مطابق تحفہ کیسے حاصل کریں:\n1. اس بات کا تعین کریں کہ آیا صارف اپنی موجودہ دولت کی سطح کی بنیاد پر حسب ضرورت تحفہ وصول کرنے کا اہل ہے۔\n(1) جب صارف کی دولت کی سطح ≥35 ہو: صارف حسب ضرورت تحفہ کے لیے ایک بار ویڈیو اپ لوڈ کر سکتا ہے۔ ہم صارف کی موجودہ پروفائل تصویر کو بطور تحفہ تصویر اور فراہم کردہ ویڈیو کو \"حسب ضرورت\" پر تحفہ اثر کے طور پر استعمال کریں گے۔ پیداوار میں کچھ وقت لگے گا، اور اسٹور میں دستیاب ہوتے ہی ہم آپ کو مطلع کریں گے۔\n(2) جب صارف کی دولت کی سطح ≥45 ہو: صارف حسب ضرورت تحفہ کے لیے ایک بار ویڈیو اپ لوڈ کر سکتا ہے۔ ہم صارف کی موجودہ پروفائل تصویر کو بطور تحفہ تصویر اور فراہم کردہ ویڈیو کو \"حسب ضرورت\" پر تحفہ اثر کے طور پر استعمال کریں گے۔ پیداوار میں کچھ وقت لگے گا، اور اسٹور میں دستیاب ہوتے ہی ہم آپ کو مطلع کریں گے۔\n2. آپ ایپ پر مخصوص سرگرمیوں میں حصہ لے سکتے ہیں اور معیار کو پورا کرنے کے بعد، \"پیغام\" → \"ہم سے رابطہ کریں\" سیکشن کے ذریعے ہم سے رابطہ کر سکتے ہیں۔ سرگرمی کے اسکرین شاٹس اور ویڈیو فراہم کریں جسے آپ حسب ضرورت تحفہ کے لیے استعمال کرنا چاہتے ہیں۔ ہم آپ کی موجودہ پروفائل تصویر کو بطور تحفہ تصویر اور فراہم کردہ ویڈیو کو بطور تحفہ اثر استعمال کریں گے، پھر اسے \"حسب ضرورت\" کے تحت درج کریں۔ پیداوار میں کچھ وقت لگے گا، اور اسٹور میں دستیاب ہوتے ہی ہم آپ کو مطلع کریں گے۔\nحسب ضرورت گفٹ کی میعاد کی مدت اور اسے کیسے بڑھایا جائے:\nخصوصی حسب ضرورت تحفے شیلف پر 30 دن کی میعاد کے لیے دستیاب ہوں گے۔ بااثر اور متاثر کن صارفین کو اس نئے تجربے سے لطف اندوز ہونے اور اپنے ذاتی انداز کو ظاہر کرنے کے قابل بنانے کے لیے، وہ صارفین جو حسب ضرورت تحائف کے مالک ہیں وہ کسی بھی مہینے میں $500 کا ری چارج کر کے اپنے تمام حسب ضرورت تحائف کے شیلف ٹائم کو 30 دن تک بڑھا سکتے ہیں۔", + "customizedGiftRules": "حسب ضرورت گفٹ رولز", + "rulesUpload": "قواعد اور اپ لوڈ کریں۔", + "monthly": "ماہانہ", + "playLog": "پلے لاگ:", + "selectACountry": "ایک ملک منتخب کریں:", + "message": "پیغام", + "clearCache": "کیشے صاف کریں۔", + "customized": "اپنی مرضی کے مطابق", + "searchInputHint": "اکاؤنٹ/کمرہ نمبر درج کریں۔", + "kickRoomTips": "تمہیں کمرے سے نکال دیا گیا ہے۔", + "joinRoomTips": "کمرے میں شمولیت اختیار کی!", + "roomSetting": "کمرے کی ترتیب", + "roomDetails": "کمرے کی تفصیلات", + "systemRoomTips": "براہ کرم شائستگی اور احترام کو برقرار رکھیں۔ اسلان پر کوئی بھی فحش یا بیہودہ مواد سختی سے ممنوع ہے۔ خلاف ورزی میں پائے جانے والے کسی بھی اکاؤنٹ پر مستقل پابندی لگا دی جائے گی۔ ہم تمام صارفین سے درخواست کرتے ہیں کہ وہ اسلان کی کمیونٹی گائیڈ لائنز کی شعوری پابندی کریں۔", + "copiedToClipboard": "کلپ بورڈ پر کاپی ہو گیا۔", + "recharge": "ریچارج کریں۔", + "receivedFromALuckyGift": "ایک خوش قسمت تحفہ سے ملا۔", + "followedYou": "آپ کی پیروی کی۔", + "agentCenter": "ایجنسی سینٹر", + "areYouSureYouWantToClearLocalCache": "کیا آپ واقعی مقامی کیش کو صاف کرنا چاہتے ہیں؟", + "clearMessage": "اسکرین پیغامات کو صاف کریں۔", + "report": "رپورٹ", + "coins3": "سکے", + "giftSpecialEffects": "تحفے کے خصوصی اثرات", + "basicFeatures": "بنیادی خصوصیات", + "task": "کام", + "importantReminder": "اہم یاد دہانی", + "entryVehicleAnimation": "انٹری وہیکل اینیمیشن (VIP3)", + "floatingAnimationInGlobal": "عالمی سطح پر فلوٹنگ اینیمیشن", + "entryVehicleAnimation2": "VlP3 یا اس سے زیادہ مراعات کے حامل صارفین گاڑی کی اینیمیشن کو غیر فعال کرنے کے لیے فنکشن کا استعمال کر سکتے ہیں۔", + "dailyTasks": "روزانہ کے کام", + "enterRoomConfirmTips": "کیا آپ واقعی کمرے میں داخل ہونا چاہتے ہیں؟", + "followSucc": "کامیابی سے پیروی کی گئی۔", + "goldListort": "گولڈ لسٹ", + "rechargeList": "ریچارج کی فہرست", + "edit": "ترمیم کریں۔", + "swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt": "*تجویز: فلوٹنگ اسکرین ایریا کو تیزی سے بند کرنے کے لیے بائیں جانب سوائپ کریں۔", + "enterThisVoiceChatRoom": "اس وائس چیٹ روم میں داخل ہوں؟", + "go": "جاؤ", + "done": "ہو گیا", + "improvementTasks": "بہتری کے کام", + "save": "محفوظ کریں۔", + "kickPrevention": "کک کی روک تھام", + "freeChatSpeak": "مفت چیٹ اور بات کریں۔", + "vipExclusiveVehicles": "وی آئی پی خصوصی گاڑیاں", + "nickName": "عرفی نام", + "buyVip": "وی آئی پی خریدیں۔", + "gender": "جنس", + "unFollow": "پیروی ختم کریں۔", + "days": "دن", + "permanent": "مستقل", + "yourVipWillExpire": "آپ کا VIP {1} کو ختم ہو جائے گا", + "cantBuyVip": "خریداری جاری رکھنے سے قاصر ہے۔", + "country": "ملک", + "birthday": "سالگرہ", + "man": "آدمی", + "woman": "عورت", + "apple": "سیب", + "google": "گوگل", + "idIcon": "آئی ڈی آئیکن", + "inviteToBecomeAHost": "میزبان بننے کے لیے مدعو کریں۔", + "currentLevelPrivilegesAndCostumes": "موجودہ سطح کے مراعات اور ملبوسات: {1}", + "uploadGifAvatar": "GlF اوتار (VIP2) اپ لوڈ کریں", + "uploadProfilePicture": "پروفائل تصویر اپ لوڈ کریں۔", + "permissionSettings": "اجازت کی ترتیبات", + "vipMicSoundWave": "VIP مائک ساؤنڈ ویو", + "startVoiceParty": "ایک آواز پارٹی شروع کریں!", + "enterRoomTips": "{1} کمرے میں داخل ہوں۔", + "roomName": "کمرے کا نام", + "roomMember": "روم ممبر", + "pleaseSelectYourCountry": "براہ کرم اپنا ملک منتخب کریں۔", + "pleaseSelectYourGender": "براہ کرم اپنی جنس منتخب کریں۔", + "pleaseEnterNickname": "براہ کرم ایک عرفی نام درج کریں۔", + "countryRegion": "ملک اور علاقہ", + "dateOfBirth": "تاریخ پیدائش", + "mute": "خاموش", + "exit": "باہر نکلیں۔", + "mysteriousInvisibility": "پراسرار پوشیدگی", + "antiBlock": "اینٹی بلاک", + "createFamilyForFree": "مفت میں فیملی بنائیں", + "privateChat": "پرائیویٹ چیٹ", + "vipBirthdayGift": "VIP سالگرہ کا تحفہ", + "storeDiscount": "اسٹور ڈسکاؤنٹ {1} آف", + "membershipFreeChatSpeak": "ممبر شپ فری چیٹ اینڈ اسپیک", + "priorityRoomSorting": "ترجیحی کمرے کی چھانٹی", + "userColoredID": "صارف کی رنگین ID", + "vipEmoticon": "VIP جذباتی نشان ({1})", + "pleaseSelectaItem": "براہ کرم ایک آئٹم منتخب کریں۔", + "areYouRureRoRecharge": "کیا آپ ریچارج کرنے کا یقین رکھتے ہیں؟", + "mInimize": "رکھو", + "everyone": "ہر کوئی", + "shop": "دکان", + "roomOwner": "کمرے کا مالک", + "dailyTaskRewardBonus": "ڈیلی ٹاسک ریوارڈ بونس ({1} XP)", + "userLevelXPBoost": "یوزر لیول ایکس پی بوسٹ ({1} XP)", + "pleaseUpgradeYourVipLevel": "براہ کرم اپنے VIP لیول کو اپ گریڈ کریں۔", + "andAboveUsers": "{1} اور اوپر والے صارفین", + "privileges": "{1} مراعات", + "basicPermissions": "بنیادی اجازتیں۔", + "hostCenter": "میزبان مرکز", + "allOnMicrophone": "سب مائیک پر", + "usersOnMicrophone": "مائیک پر صارفین", + "allInTheRoom": "سب کمرے میں", + "send": "بھیجیں۔", + "crop": "فصل", + "goToUpgrade": "اپ گریڈ پر جائیں۔", + "exclusiveEmojiWillBeReleasedAfterBecoming": "خصوصی ایموجی بننے کے بعد جاری کیا جائے گا۔", + "preventBeingBlocked": "بلاک ہونے سے روکیں۔", + "enableRankIncognitoMode": "رینک انکوگنیٹو موڈ کو فعال کریں۔", + "avoidBeingKicked": "لات مارنے سے بچیں۔", + "vipPrivilege": "VIP استحقاق", + "finish": "ختم کرنا", + "takeTheMic": "مائیک لے لو", + "openTheMic": "مائیک کھولیں۔", + "muteTheMic": "مائیک خاموش کریں۔", + "unlockTheMic": "مائیک کو غیر مقفل کریں۔", + "leavelTheMic": "مائیک چھوڑ دیں۔", + "lockTheMic": "مائیک لاک کریں۔", + "removeTheMic": "مائیک ہٹا دیں۔", + "inviteToTheMicrophone": "مائیکروفون پر مدعو کریں۔", + "openUserProfleCard": "صارف پروفائل کارڈ کھولیں۔", + "obtain": "حاصل کریں", + "win2": "جیتیں {1}", + "backTheRoom": "پیچھے کا کمرہ", + "toConsume": "استعمال کرنا", + "howToUpgrade": "اپ گریڈ کیسے کریں؟", + "spendCoinsToGainExperiencePoints": "تجربہ پوائنٹس حاصل کرنے کے لیے سکے خرچ کریں۔", + "higherLevelFancierAvatarFrame": "اعلیٰ سطح، فینسیئر بیجز/اوتار فریم", + "medalAndAvatarFrameRewards": "میڈل اور اوتار فریم انعامات", + "all": "تمام", + "gift": "تحفہ", + "chat": "گپ شپ", + "owner": "مالک", + "store": "اسٹور", + "admin": "ایڈمن", + "member": "ممبر", + "guest": "مہمان", + "submit": "جمع کروائیں۔", + "claim": "دعویٰ", + "complete": "مکمل", + "shareTo": "سے شئیر کریں۔", + "copyLink": "لنک کاپی کریں۔", + "faceBook": "فیس بک", + "whatsApp": "واٹس ایپ", + "snapChat": "سنیپ چیٹ", + "taskNamePersonalGameConsume": "کھیل ہی کھیل میں خرچ", + "taskNameRoomNewMember": "نئے کمرے کے ممبران", + "taskNameRoomOwnerSendRedPacket": "کمرے کا مالک ایک سرخ پیکٹ بھیجتا ہے۔", + "taskNameRoomOwnerSendGiftUser": "کمرے کا مالک تحفہ بھیجتا ہے۔", + "taskNameRoomMicUser120Min": "مائیک پر 120+ منٹ والے اراکین", + "taskNameRoomMicUser60Min": "مائیک پر 60+ منٹ والے اراکین", + "taskNameRoomMicUser30Min": "مائیک پر 30+ منٹ والے اراکین", + "taskNamePersonalSendGift": "صارف کو تحفہ بھیجیں۔", + "taskNameRoomOnlineUserCount": "کمرہ آن لائن ممبران", + "taskNameRoomOwnerInviteMic": "ممبر کو مائک پر مدعو کریں۔", + "taskNameRoomUserSendGiftGold": "اراکین کی طرف سے تحفے میں سکے", + "taskNameRoomOwnerSendGiftGold": "کمرے کے مالک کی طرف سے تحفے میں دیئے گئے سکے", + "taskNamePersonalMagicGiftGold": "جادوئی تحائف بھیج کر سکے جیتے۔", + "taskNamePersonalLuckyGiftGold": "لکی گفٹ بھیج کر سکے جیتے۔", + "taskNameRoomOwnerMicTime": "کمرے کا مالک کمرے میں مائیک چلا رہا ہے۔", + "taskNamePersonalActiveInRoom": "دوسروں کے کمروں میں متحرک رہیں", + "taskNamePersonalMicInRoom": "مائیک پر جائیں۔", + "dailyCoinBonanzaRulesDetail": "1. روزانہ ذاتی کام اور روزانہ کمرے کے مالک کے کام فی صارف فی دن ایک بار مکمل کیے جا سکتے ہیں۔ سعودی وقت کے مطابق 00:00:00 پر ٹاسکس ری سیٹ ہو گئے۔\n2. روزانہ ذاتی کام صرف دوسروں کے کمروں میں مکمل کیے جاسکتے ہیں۔ کمرے کے مالک کے کام صرف آپ کے اپنے کمرے میں مکمل کیے جا سکتے ہیں۔\n3. تحفہ دینے کے کام صرف کمروں میں بھیجے گئے تحائف کو شمار کرتے ہیں، فیڈ پر بھیجے گئے تحائف کو نہیں۔\n4. اگر کسی بھی طرح سے ایک ہی ڈیوائس یا ایک ہی سم کارڈ کا استعمال کرتے ہوئے متعدد اکاؤنٹس بنائے گئے ہیں، تو تمام کاموں کے لیے انعامات کا صرف ایک بار دعوی کیا جا سکتا ہے۔", + "dailyCoinBonanzaRules": "ڈیلی کوائن بونانزا کے قواعد", + "roomOwnerTasks": "کمرے کے مالک کے کام", + "personalTasks": "ذاتی کام", + "noPromptsToday": "آج کوئی اشارہ نہیں ہے۔", + "getPaidToRefer": "حوالہ دینے کے لیے ادائیگی کریں۔", + "membershipFee": "رکنیت کی فیس", + "membershipFeeTips1": "براہ کرم اپنے کمرے کے لیے رکنیت کی فیس مقرر کریں۔ صارفین فیس ادا کر کے آپ کے کمرے میں شامل ہو سکتے ہیں۔", + "membershipFeeTips2": "کمرے کا رکن بننے کے لیے صارف کے لیے مطلوبہ سونا۔ کمرے کے مالک کو سونے کا 50% ملے گا۔", + "freePrice": "فیس: 0-10000", + "touristsSendText": "سیاح متن بھیجیں۔", + "touristsTakeToTheMic": "سیاح مائیک لے جاتے ہیں۔", + "theMembershipFee": "رکنیت کی فیس", + "theModificationsMade": "اس بار کی گئی ترمیم باہر نکلنے کے بعد محفوظ نہیں ہوگی۔", + "viewFrame": "فریم دیکھیں", + "enterRoomName": "کمرے کا نام درج کریں۔", + "headdress": "فریم", + "mountains": "گاڑیاں", + "purchaseIsSuccessful": "خریداری کامیاب ہے۔", + "buy": "خریدیں۔", + "followed": "پیروی کی۔", + "follow2": "پیروی کریں:{1}", + "fans2": "پرستار:{1}", + "vistors2": "دیکھنے والے:{1}", + "personal2": "ذاتی:", + "family2": "خاندان:", + "conntinue": "جاری رکھیں", + "confirmBuyTips": "کیا آپ واقعی خریدنا چاہتے ہیں؟", + "purchase": "خریداری", + "setRoomPassword": "کمرے کا پاس ورڈ سیٹ کریں۔", + "inputRoomPassword": "کمرے کا پاس ورڈ درج کریں۔", + "enter": "داخل کریں۔", + "createDynamicSuccess": "کامیابی کے ساتھ متحرک بنایا گیا۔", + "deleteDynamicTips": "کیا آپ واقعی اس ڈائنامک کو حذف کرنا چاہتے ہیں؟", + "deleteCommentTips": "کیا آپ واقعی یہ تبصرہ حذف کرنا چاہتے ہیں؟", + "deleteSuccessful": "حذف کرنا کامیاب!", + "itemsLeft": "آئٹمز رہ گئے۔", + "password": "پاس ورڈ", + "replySucc": "جواب کامیاب", + "comment": "تبصرہ", + "showMore": "مزید دکھائیں", + "showLess": "کم دکھائیں۔", + "enterPassword": "پاس ورڈ درج کریں۔", + "enterAccount": "اکاؤنٹ درج کریں۔", + "logIn": "لاگ ان کریں۔", + "saySomething": "کچھ بولو...", + "sayHi": "ہیلو کہو..", + "pleaseChatFfriendly": "براہ کرم دوستانہ بات چیت کریں۔", + "unLockTheRoom": "کمرے کو غیر مقفل کریں۔", + "operationSuccessful": "آپریشن کامیاب رہا۔", + "adminByHomeowner": "گھر کے مالک کے ذریعہ بطور منتظم مقرر کیا گیا ہے۔", + "memberByHomeowner": "گھر کے مالک کے ذریعہ ممبر کے طور پر سیٹ کیا جاتا ہے۔", + "touristByHomeowner": "گھر کے مالک کی طرف سے ایک سیاح کے طور پر مقرر کیا گیا ہے.", + "becomeHost": "میزبان بننے کے لیے درخواست دیں۔", + "superFans": "سپر شائقین:", + "setUpAnIdentity": "ایک شناخت قائم کریں۔", + "kickedOutOfRoom": "کمرے سے باہر نکال دیا۔", + "playGiftMusicAndDynamicMusic": "گفٹ میوزک اور ڈائنامک میوزک چلائیں۔", + "knapsack": "نیپ سیک", + "bdLeader": "بی ڈی لیڈر", + "picture": "تصویر", + "theImageSizeCannotExceed": "اپ لوڈ ناکام: فائل 2MB سے کم ہونی چاہیے۔", + "activity": "سرگرمی", + "alreadyAnAdministrator": "پہلے سے ایڈمنسٹریٹر", + "alreadyAnMember": "پہلے سے ہی ایک رکن ہے۔", + "alreadyAnTourist": "پہلے سے ہی ایک سیاح", + "touristsCannotSendMessages": "سیاح پیغامات نہیں بھیج سکتے", + "touristsAreNotAllowedToGoOnTheMic": "سیاحوں کو مائیک پر جانے کی اجازت نہیں ہے۔", + "lockTheRoom": "کمرے کو لاک کریں۔", + "special": "خاص", + "visitorList": "وزیٹر لسٹ", + "successfulWear": "کامیاب لباس", + "confirmUnUseTips": "کیا آپ اسے ہٹانے کی تصدیق کرتے ہیں؟", + "custom": "حسب ضرورت", + "myItems": "میری اشیاء", + "use": "استعمال کریں۔", + "unUse": "غیر استعمال", + "renewal": "تجدید", + "wallet": "پرس", + "profile": "پروفائل", + "giftwall": "گفٹ وال", + "announcement": "اعلان", + "blockedList": "مسدود فہرست", + "country2": "ملک:", + "sendTo": "کو بھیجیں۔", + "medals": "تمغے", + "activityMedal": "سرگرمی میڈل", + "achievementMedal": "اچیومنٹ میڈل", + "credits": "کریڈٹس: {1}", + "successfullyUnloaded": "کامیابی کے ساتھ اتار لیا گیا۔", + "expired": "میعاد ختم", + "day": "دن", + "inUse": "استعمال میں", + "confirmUseTips": "کیا آپ اس کے استعمال کی تصدیق کرنا چاہتے ہیں؟", + "pleaseUploadUserAvatar": "براہ کرم ایک اوتار اپ لوڈ کریں۔", + "joinMemberTips": "اگر آپ کمرے میں مہمان ہیں، تو آپ مائیکروفون نہیں لے سکتے۔", + "giftGivingSuccessful": "تحفہ دینا کامیاب۔", + "theAccountPasswordCannotBeEmpty": "اکاؤنٹ یا پاس ورڈ خالی نہیں ہو سکتا۔", + "invitesYouToTheMicrophone": "{1} آپ کو مائیکروفون پر مدعو کرتا ہے۔", + "english": "انگریزی", + "chinese": "چینی", + "arabic": "عربی", + "darkMode": "ڈارک موڈ", + "lightMode": "لائٹ موڈ", + "systemDefault": "سسٹم ڈیفالٹ", + "pleaseGetOnTheMicFirst": "براہ کرم پہلے مائیک پر جائیں۔", + "duration2": "دورانیہ:{1}" +} diff --git a/atu_images/index/at_icon_user_card_copy_id.png b/atu_images/index/at_icon_user_card_copy_id.png new file mode 100644 index 0000000..7f75a16 Binary files /dev/null and b/atu_images/index/at_icon_user_card_copy_id.png differ diff --git a/atu_images/index/at_person_detail_bag.png b/atu_images/index/at_person_detail_bag.png new file mode 100644 index 0000000..0ab83e7 Binary files /dev/null and b/atu_images/index/at_person_detail_bag.png differ diff --git a/atu_images/room/anim/.gitkeep b/atu_images/room/anim/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/atu_images/room/anim/.gitkeep @@ -0,0 +1 @@ + diff --git a/atu_images/room/at_icon_room_rocket_record_bg.png b/atu_images/room/at_icon_room_rocket_record_bg.png new file mode 100644 index 0000000..eaf5b50 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_record_bg.png differ diff --git a/atu_images/room/at_icon_room_rocket_yellow_bg.png b/atu_images/room/at_icon_room_rocket_yellow_bg.png new file mode 100644 index 0000000..32ea2f8 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_yellow_bg.png differ diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/fonts/.gitkeep b/fonts/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/fonts/.gitkeep @@ -0,0 +1 @@ + diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 1dc6cf7..391a902 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 13.0 diff --git a/ios/Podfile b/ios/Podfile index a09ea13..903207a 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '13.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index cc87936..e1c2981 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,50 +1,10 @@ PODS: - - agora_rtc_engine (6.5.3): - - AgoraIrisRTC_iOS (= 4.5.2-build.1) - - AgoraRtcEngine_iOS (= 4.5.2) + - agora_rtc_engine (6.5.4): + - AgoraIrisRTC_iOS (= 4.5.3-build.1) + - AgoraRtcEngine_Special_iOS (= 4.5.3.70) - Flutter - - AgoraInfra_iOS (1.2.13.1) - - AgoraIrisRTC_iOS (4.5.2-build.1) - - AgoraRtcEngine_iOS (4.5.2): - - AgoraRtcEngine_iOS/AIAEC (= 4.5.2) - - AgoraRtcEngine_iOS/AIAECLL (= 4.5.2) - - AgoraRtcEngine_iOS/AINS (= 4.5.2) - - AgoraRtcEngine_iOS/AINSLL (= 4.5.2) - - AgoraRtcEngine_iOS/AudioBeauty (= 4.5.2) - - AgoraRtcEngine_iOS/ClearVision (= 4.5.2) - - AgoraRtcEngine_iOS/ContentInspect (= 4.5.2) - - AgoraRtcEngine_iOS/FaceCapture (= 4.5.2) - - AgoraRtcEngine_iOS/FaceDetection (= 4.5.2) - - AgoraRtcEngine_iOS/LipSync (= 4.5.2) - - AgoraRtcEngine_iOS/ReplayKit (= 4.5.2) - - AgoraRtcEngine_iOS/RtcBasic (= 4.5.2) - - AgoraRtcEngine_iOS/SpatialAudio (= 4.5.2) - - AgoraRtcEngine_iOS/VideoAv1CodecDec (= 4.5.2) - - AgoraRtcEngine_iOS/VideoAv1CodecEnc (= 4.5.2) - - AgoraRtcEngine_iOS/VideoCodecDec (= 4.5.2) - - AgoraRtcEngine_iOS/VideoCodecEnc (= 4.5.2) - - AgoraRtcEngine_iOS/VirtualBackground (= 4.5.2) - - AgoraRtcEngine_iOS/VQA (= 4.5.2) - - AgoraRtcEngine_iOS/AIAEC (4.5.2) - - AgoraRtcEngine_iOS/AIAECLL (4.5.2) - - AgoraRtcEngine_iOS/AINS (4.5.2) - - AgoraRtcEngine_iOS/AINSLL (4.5.2) - - AgoraRtcEngine_iOS/AudioBeauty (4.5.2) - - AgoraRtcEngine_iOS/ClearVision (4.5.2) - - AgoraRtcEngine_iOS/ContentInspect (4.5.2) - - AgoraRtcEngine_iOS/FaceCapture (4.5.2) - - AgoraRtcEngine_iOS/FaceDetection (4.5.2) - - AgoraRtcEngine_iOS/LipSync (4.5.2) - - AgoraRtcEngine_iOS/ReplayKit (4.5.2) - - AgoraRtcEngine_iOS/RtcBasic (4.5.2): - - AgoraInfra_iOS (= 1.2.13.1) - - AgoraRtcEngine_iOS/SpatialAudio (4.5.2) - - AgoraRtcEngine_iOS/VideoAv1CodecDec (4.5.2) - - AgoraRtcEngine_iOS/VideoAv1CodecEnc (4.5.2) - - AgoraRtcEngine_iOS/VideoCodecDec (4.5.2) - - AgoraRtcEngine_iOS/VideoCodecEnc (4.5.2) - - AgoraRtcEngine_iOS/VirtualBackground (4.5.2) - - AgoraRtcEngine_iOS/VQA (4.5.2) + - AgoraIrisRTC_iOS (4.5.3-build.1) + - AgoraRtcEngine_Special_iOS (4.5.3.70) - app_links (6.4.1): - Flutter - AppAuth (1.7.6): @@ -132,13 +92,6 @@ PODS: - Mantle - SDWebImage - SDWebImageWebPCoder - - flutter_inappwebview_ios (0.0.1): - - Flutter - - flutter_inappwebview_ios/Core (= 0.0.1) - - OrderedSet (~> 6.0.3) - - flutter_inappwebview_ios/Core (0.0.1): - - Flutter - - OrderedSet (~> 6.0.3) - fluttertoast (0.0.2): - Flutter - google_sign_in_ios (0.0.1): @@ -223,13 +176,9 @@ PODS: - on_audio_query_ios (0.0.1): - Flutter - SwiftyBeaver - - OrderedSet (6.0.3) - package_info_plus (0.4.5): - Flutter - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - permission_handler_apple (9.3.0): + - permission_handler_apple (9.4.8): - Flutter - PromisesObjC (2.4.0) - PromisesSwift (2.4.0): @@ -289,7 +238,6 @@ DEPENDENCIES: - Flutter (from `Flutter`) - flutter_foreground_task (from `.symlinks/plugins/flutter_foreground_task/ios`) - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) - - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`) - image_cropper (from `.symlinks/plugins/image_cropper/ios`) @@ -299,7 +247,6 @@ DEPENDENCIES: - loading_indicator_view_plus (from `.symlinks/plugins/loading_indicator_view_plus/ios`) - on_audio_query_ios (from `.symlinks/plugins/on_audio_query_ios/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`) @@ -316,9 +263,8 @@ DEPENDENCIES: SPEC REPOS: trunk: - - AgoraInfra_iOS - AgoraIrisRTC_iOS - - AgoraRtcEngine_iOS + - AgoraRtcEngine_Special_iOS - AppAuth - AppCheckCore - Firebase @@ -341,7 +287,6 @@ SPEC REPOS: - libwebp - Mantle - nanopb - - OrderedSet - PromisesObjC - PromisesSwift - QGVAPlayer @@ -373,8 +318,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_foreground_task/ios" flutter_image_compress_common: :path: ".symlinks/plugins/flutter_image_compress_common/ios" - flutter_inappwebview_ios: - :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" google_sign_in_ios: @@ -393,8 +336,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/on_audio_query_ios/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" shared_preferences_foundation: @@ -423,10 +364,9 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: - agora_rtc_engine: 0c7d50312967c4dc31c3c45e50589ce48f57e08a - AgoraInfra_iOS: 3691b2b277a1712a35ae96de25af319de0d73d08 - AgoraIrisRTC_iOS: eab58c126439adf5ec99632828a558ea216860da - AgoraRtcEngine_iOS: 97e2398a2addda9057815a2a583a658e36796ff6 + agora_rtc_engine: c8ad6313ec7a1b2f28ab3328ff485df6d19a32de + AgoraIrisRTC_iOS: d6468160cad25d36c2b6cb5e9bd5b17ba4e58677 + AgoraRtcEngine_Special_iOS: 48063561d21b07524358ee0a17abc6be762c58f7 app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73 AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f @@ -449,7 +389,6 @@ SPEC CHECKSUMS: Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_foreground_task: a159d2c2173b33699ddb3e6c2a067045d7cebb89 flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1 - flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 @@ -459,25 +398,23 @@ SPEC CHECKSUMS: GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 HydraAsync: 8d589bd725b0224f899afafc9a396327405f8063 image_cropper: 655b3ba703c9e15e3111e79151624d6154288774 - image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a - in_app_purchase_storekit: d1a48cb0f8b29dbf5f85f782f5dd79b21b90a5e6 + image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 + in_app_purchase_storekit: 22cca7d08eebca9babdf4d07d0baccb73325d3c8 iris_method_channel: 7d661cf3259b3009ae423508470dbeb9374446ee libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 loading_indicator_view_plus: 33a5d3527e9c6a7b470c79878613d01c4fa1fb65 Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 on_audio_query_ios: 28a780e2d0d85d92d500ba6e12c6c8167022b2fa - OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + permission_handler_apple: 92d754bbaa7361d436db2d6c3c1c2a0fdcec462e PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 QGVAPlayer: a0bca68c9bd6f1c8de5ac2d10ddf98be6038cce9 RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377 - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418 social_sharing_plus: e6024862e5a4be59ef8c97a93558cba1043628bf sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 @@ -487,12 +424,12 @@ SPEC CHECKSUMS: tencent_cloud_chat_sdk: 55e5fffe20f6b7937a26a674ccccb639563a9790 TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 TXIMSDK_Plus_iOS_XCFramework: 5d1933192fb3b7ef2fe933f1623de4a0486a7fe2 - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d - video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b + url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b + video_player_avfoundation: 3453f792138786248960ca029747fcd9f318ef52 video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140 wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 - webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2 + webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d -PODFILE CHECKSUM: a6f49a93e5f85201a2efdadcd7cf184b0e310894 +PODFILE CHECKSUM: 8de56b17e20396b0ad891231e52eec09fd896d54 COCOAPODS: 1.16.2 diff --git a/lib/app_localizations.dart b/lib/app_localizations.dart index 4a6135a..88a3fc3 100644 --- a/lib/app_localizations.dart +++ b/lib/app_localizations.dart @@ -744,6 +744,23 @@ class ATAppLocalizations { String get roomRocketHelpTips => translate('roomRocketHelpTips'); + String get roomRocketRecordAction => translate('roomRocketRecordAction'); + + String get roomRocketRewardTop1 => translate('roomRocketRewardTop1'); + + String get roomRocketRewardSetOff => translate('roomRocketRewardSetOff'); + + String get roomRocketRewardInRoom => translate('roomRocketRewardInRoom'); + + String get roomRocketResetCountdown => translate('roomRocketResetCountdown'); + + String get roomRocketRecordRoom => translate('roomRocketRecordRoom'); + + String get roomRocketRecordReward => translate('roomRocketRecordReward'); + + String get roomRocketRecordLast35Days => + translate('roomRocketRecordLast35Days'); + String get giveUpIdentity => translate('giveUpIdentity'); String get bdLeader => translate('bdLeader'); @@ -1657,8 +1674,17 @@ class _ATAppLocalizationsDelegate const _ATAppLocalizationsDelegate(); @override - bool isSupported(Locale locale) => - ['en', 'zh', 'ar','bn','tr'].contains(locale.languageCode); + bool isSupported(Locale locale) => [ + 'en', + 'zh', + 'ar', + 'bn', + 'tr', + 'id', + 'fil', + 'ur', + 'hi', + ].contains(locale.languageCode); @override Future load(Locale locale) async { diff --git a/lib/chatvibe_core/config/configs/variant1_config.dart b/lib/chatvibe_core/config/configs/variant1_config.dart index a292817..1f63eff 100644 --- a/lib/chatvibe_core/config/configs/variant1_config.dart +++ b/lib/chatvibe_core/config/configs/variant1_config.dart @@ -14,7 +14,7 @@ class Variant1Config implements AppConfig { String get packageName => 'com.chat.auu'; @override - String get apiHost => 'https://api.atuchat.com/'; // 独立API服务器,需要部署 + String get apiHost => 'https://aslanapi-test.haiyihy.com/';//'https://api.atuchat.com/'; // 独立API服务器,需要部署 @override String get imgHost => 'https://img.atuchat.com/'; // 独立图片服务器,需要部署 diff --git a/lib/chatvibe_core/utilities/at_banner_utils.dart b/lib/chatvibe_core/utilities/at_banner_utils.dart index e33b46d..61954ab 100644 --- a/lib/chatvibe_core/utilities/at_banner_utils.dart +++ b/lib/chatvibe_core/utilities/at_banner_utils.dart @@ -8,7 +8,15 @@ import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; import '../../chatvibe_data/models/enum/at_banner_open_type.dart'; class ATBannerUtils { - static void openBanner(ATIndexBannerRes item, BuildContext context) { + static bool isWebBanner(ATIndexBannerRes item) { + return item.content != ATBannerOpenType.ENTER_ROOM.name && + ATStringUtils.isUrl(item.params ?? ""); + } + + static Future openBanner( + ATIndexBannerRes item, + BuildContext context, + ) async { if (item.content == ATBannerOpenType.ENTER_ROOM.name) { var params = item.params; if (params != null && params.isNotEmpty) { @@ -17,7 +25,7 @@ class ATBannerUtils { } else { var params = item.params; if (ATStringUtils.isUrl(params ?? "")) { - ATNavigatorUtils.push( + await ATNavigatorUtils.push( context, "${MainRoute.webViewPage}?url=${Uri.encodeComponent(params ?? "")}&showTitle=false", replace: false, diff --git a/lib/chatvibe_data/models/enum/at_banner_type.dart b/lib/chatvibe_data/models/enum/at_banner_type.dart index 93dd890..846fb38 100644 --- a/lib/chatvibe_data/models/enum/at_banner_type.dart +++ b/lib/chatvibe_data/models/enum/at_banner_type.dart @@ -1,6 +1 @@ -enum ATBannerType{ - EXPLORE_PAGE, - HOME_ALERT, - GAME, - ROOM -} \ No newline at end of file +enum ATBannerType { EXPLORE_PAGE, HOME_ALERT, GAME, ROOM, ROOM_LIST } diff --git a/lib/chatvibe_data/sources/remote/net/api.dart b/lib/chatvibe_data/sources/remote/net/api.dart index 7838754..28969eb 100644 --- a/lib/chatvibe_data/sources/remote/net/api.dart +++ b/lib/chatvibe_data/sources/remote/net/api.dart @@ -191,6 +191,11 @@ class BaseNetworkClient { // 错误处理 DioException _handleDioError(DioException e) { ATLoadingManager.veilRoutine(); + final responseData = e.response?.data; + final errorCode = + responseData is Map ? responseData["errorCode"] : null; + final errorMsg = + responseData is Map ? responseData["errorMsg"] : null; switch (e.type) { case DioExceptionType.connectionTimeout: return DioException(requestOptions: e.requestOptions, error: '连接超时'); @@ -199,8 +204,6 @@ class BaseNetworkClient { case DioExceptionType.receiveTimeout: return DioException(requestOptions: e.requestOptions, error: '接收超时'); case DioExceptionType.badResponse: - var errorCode = e.response?.data["errorCode"]; - var errorMsg = e.response?.data["errorMsg"]; if (errorCode == ATErroCode.userNotRegistered.code) { //用户还没有注册 ATTts.show("Please register an account first."); @@ -282,6 +285,8 @@ class BaseNetworkClient { default: return DioException( requestOptions: e.requestOptions, + response: e.response, + type: e.type, error: 'Net fail', ); } diff --git a/lib/chatvibe_data/sources/repositories/config_repository_imp.dart b/lib/chatvibe_data/sources/repositories/config_repository_imp.dart index e98e2fd..2721d07 100644 --- a/lib/chatvibe_data/sources/repositories/config_repository_imp.dart +++ b/lib/chatvibe_data/sources/repositories/config_repository_imp.dart @@ -34,10 +34,10 @@ class ConfigRepositoryImp implements ChatVibeConfigRepository { ///sys/config/banner @override - Future> getBanner({List?types}) async { + Future> getBanner({List? types}) async { Map params = {}; if (types != null) { - params["types"] = types; + params["types"] = types.length == 1 ? types.first : types; } final result = await http.get( "0d8319c7a696c73d58f5e0ae304dc663f574ade4154ed90ccaee524f6ba14490", @@ -182,7 +182,9 @@ class ConfigRepositoryImp implements ChatVibeConfigRepository { "1585c72b88d0d249c7078aae852a0806cc3d8e483b8d861962a977a00523180a", fromJson: (json) => - (json as List).map((e) => ATProductConfigRes.fromJson(e)).toList(), + (json as List) + .map((e) => ATProductConfigRes.fromJson(e)) + .toList(), ); return result; } @@ -245,7 +247,11 @@ class ConfigRepositoryImp implements ChatVibeConfigRepository { ///sys/config/game/ranking @override - Future gameRanking(int current,String periodType, {int? size = 20}) async { + Future gameRanking( + int current, + String periodType, { + int? size = 20, + }) async { Map parm = {}; parm["current"] = current; parm["periodType"] = periodType; @@ -260,7 +266,7 @@ class ConfigRepositoryImp implements ChatVibeConfigRepository { ///sys/version/manage/release/latest @override - Future versionManageLatest() async{ + Future versionManageLatest() async { final result = await http.get( "ee9584f714ded864780e47dab2cf4a2e84ac21c90fcd0966a13d2ce9e8845eb8e580afbe66f9f0fef79429cd5c1e0687", fromJson: (json) => ATVersionManageLatestRes.fromJson(json), @@ -270,7 +276,7 @@ class ConfigRepositoryImp implements ChatVibeConfigRepository { ///sys/version/manage/latest/review @override - Future versionManageLatestReview() async{ + Future versionManageLatestReview() async { final result = await http.get( "ee9584f714ded864780e47dab2cf4a2e11ce42bdd061186d4efe3305b73f10fe574aff257ce7e668d08f4caccd1c6232", fromJson: (json) => VersionManageLatesReviewRes.fromJson(json), @@ -280,7 +286,7 @@ class ConfigRepositoryImp implements ChatVibeConfigRepository { ///sys/config/customer-service @override - Future customerService() async{ + Future customerService() async { final result = await http.get( "ba316258c14cc3ebddb6d28ec314bc5704e593861ca693058e9e98ab3114cf05", fromJson: (json) => ChatVibeUserProfile.fromJson(json), @@ -290,15 +296,15 @@ class ConfigRepositoryImp implements ChatVibeConfigRepository { ///ranking/king-games-daily-top-three @override - Future> kingGamesDailyTopThree() async{ + Future> kingGamesDailyTopThree() async { final result = await http.post>( "c0c68485b2c8a2c7eb1973ee17866bfb6015dceaae385abe0e1a1598d3af93f64eb4a9d0d7e9231cab7de49e86a5b8bc", data: {}, fromJson: (json) => - (json as List) - .map((e) => ATTopFourWithRewardRes.fromJson(e)) - .toList(), + (json as List) + .map((e) => ATTopFourWithRewardRes.fromJson(e)) + .toList(), ); return result; } diff --git a/lib/chatvibe_data/sources/repositories/room_repository_imp.dart b/lib/chatvibe_data/sources/repositories/room_repository_imp.dart index 81ad442..1dd0c01 100644 --- a/lib/chatvibe_data/sources/repositories/room_repository_imp.dart +++ b/lib/chatvibe_data/sources/repositories/room_repository_imp.dart @@ -268,6 +268,26 @@ class ChatRoomRepository implements ChatVibeRoomRepository { return result; } + ///gift/private + @override + Future givePrivateGift( + String acceptUserId, + String giftId, + num quantity, + ) async { + Map params = {}; + params["acceptUserId"] = acceptUserId; + params["giftId"] = giftId; + params["quantity"] = quantity; + + final result = await http.post( + '779a319006782e7f4b89aa3637eb7fd2', + data: params, + fromJson: (json) => json as double, + ); + return result; + } + ///live/mic/kill @override Future micKill(String roomId, num mickIndex) async { diff --git a/lib/chatvibe_domain/repositories/room_repository.dart b/lib/chatvibe_domain/repositories/room_repository.dart index 11b616d..ec508e1 100644 --- a/lib/chatvibe_domain/repositories/room_repository.dart +++ b/lib/chatvibe_domain/repositories/room_repository.dart @@ -95,6 +95,13 @@ abstract class ChatVibeRoomRepository { String? dynamicContentId, }); + ///私聊赠送礼物 + Future givePrivateGift( + String acceptUserId, + String giftId, + num quantity, + ); + ///举报 Future reported( String reportUserId, diff --git a/lib/chatvibe_features/chat/message/at_message_page.dart b/lib/chatvibe_features/chat/message/at_message_page.dart index f2b09c3..ac57dff 100644 --- a/lib/chatvibe_features/chat/message/at_message_page.dart +++ b/lib/chatvibe_features/chat/message/at_message_page.dart @@ -15,6 +15,7 @@ import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_managers/gift_system_manager.dart'; import 'package:aslan/chatvibe_ui/widgets/msg/message_conversation_list_page.dart'; import 'package:aslan/chatvibe_features/chat/at_chat_route.dart'; import 'package:aslan/chatvibe_features/chat/message/at_message_friends_page.dart'; @@ -35,12 +36,24 @@ class _MessagePageState extends State { BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; bool isFromRoom = false; bool showSystemAnnouncementTips = true; + GiftProvider? _giftProvider; + bool _roomMessagePageVisibilityApplied = false; _MessagePageState(this.isFromRoom); @override void initState() { super.initState(); + if (isFromRoom) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) { + return; + } + _giftProvider = Provider.of(context, listen: false); + _giftProvider!.updateHideLGiftAnimalOnRoomMessagePage(true); + _roomMessagePageVisibilityApplied = true; + }); + } showSystemAnnouncementTips = DataPersistence.getBool( "${AccountStorage().getCurrentUser()?.userProfile?.account}-ShowSystemAnnouncementTips", defaultValue: true, @@ -86,6 +99,12 @@ class _MessagePageState extends State { @override void dispose() { + if (_roomMessagePageVisibilityApplied) { + final giftProvider = _giftProvider; + WidgetsBinding.instance.addPostFrameCallback((_) { + giftProvider?.updateHideLGiftAnimalOnRoomMessagePage(false); + }); + } super.dispose(); } diff --git a/lib/chatvibe_features/chat/message_chat_page.dart b/lib/chatvibe_features/chat/message_chat_page.dart index 2b26de4..3159c0a 100644 --- a/lib/chatvibe_features/chat/message_chat_page.dart +++ b/lib/chatvibe_features/chat/message_chat_page.dart @@ -1,12 +1,18 @@ import 'dart:convert'; import 'dart:io'; import 'package:extended_image/extended_image.dart' - show ExtendedImage, ExtendedRawImage, ExtendedImageState, LoadState; + show + ExtendedImage, + ExtendedNetworkImageProvider, + ExtendedRawImage, + ExtendedImageState, + LoadState; import 'package:extended_text/extended_text.dart'; import 'package:extended_text_field/extended_text_field.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_ninepatch_image/flutter_ninepatch_image.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:aslan/app_localizations.dart'; @@ -48,15 +54,55 @@ import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; import 'package:aslan/chatvibe_core/constants/at_screen.dart'; import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; import 'package:aslan/chatvibe_core/utilities/at_keybord_util.dart'; +import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; import 'package:aslan/chatvibe_core/utilities/at_message_notifier.dart'; import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; import 'package:aslan/chatvibe_domain/usecases/custom_tab_selector.dart'; +import 'package:aslan/chatvibe_features/gift/gift_page.dart'; import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/effect/vapp_svga_layer_widget.dart'; import '../../chatvibe_domain/models/res/at_user_red_packet_send_res.dart'; +Map? _privateGiftDataFromJson(String? data) { + if (data == null || data.isEmpty) { + return null; + } + try { + var json = jsonDecode(data); + if (json is Map && json["type"] == "PRIVATE_GIFT") { + return json; + } + } catch (_) {} + return null; +} + +Map? _privateGiftDataFromMessage(V2TimMessage message) { + if (message.elemType != MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) { + return null; + } + V2TimCustomElem? customElem = message.customElem; + if (customElem == null) { + return null; + } + Map? data = _privateGiftDataFromJson(customElem.data); + if (data != null || customElem.desc != "PRIVATE_GIFT") { + return data; + } + return {"type": "PRIVATE_GIFT"}; +} + +void _playPrivateGiftAnimation(Map? giftData) { + String sourceUrl = "${giftData?["giftSourceUrl"] ?? ""}"; + if (sourceUrl.isEmpty || !ATGlobalConfig.isGiftSpecialEffects) { + return; + } + ATGiftVapSvgaManager().play(sourceUrl); +} + class ATMessageChatPage extends StatefulWidget { final V2TimConversation? conversation; final bool shrinkWrap; @@ -272,6 +318,12 @@ class _ATMessageChatPageState extends State { ), ), ), + if (!widget.inRoom) + Positioned.fill( + child: IgnorePointer( + child: VapPlusSvgaPlayer(tag: "room_gift"), + ), + ), ], ), ); @@ -301,77 +353,97 @@ class _ATMessageChatPageState extends State { ///消息列表 Widget _msgList() { print('xiaoxiliebiao:${currentConversationMessageList.length}'); - return SmartRefresher( - enablePullDown: false, - enablePullUp: true, - onLoading: () async { - print('onLoading'); - if (currentConversationMessageList.isNotEmpty) { - // 拉取单聊历史消息 - // 首次拉取,lastMsgID 设置为 null - // 再次拉取时,lastMsgID 可以使用返回的消息列表中的最后一条消息的id - V2TimValueCallback> v2timValueCallback = - await TencentImSDKPlugin.v2TIMManager - .getMessageManager() - .getC2CHistoryMessageList( - userID: currentConversation!.userID ?? "", - count: 30, - lastMsgID: currentConversationMessageList.last.msgID, - ); - List messages = - v2timValueCallback.data as List; - print('加载前:${currentConversationMessageList.length}'); - currentConversationMessageList.addAll(messages); - print('加载后:${currentConversationMessageList.length}'); - if (messages.length == 30) { - _refreshController.loadComplete(); - } else { - _refreshController.loadNoData(); - } - } else { - _refreshController.loadNoData(); - } - setState(() {}); - }, - footer: CustomFooter( - height: 1, - builder: (context, mode) { - return SizedBox( - height: 1, - width: 1, - child: SizedBox(height: 1, width: 1), - ); - }, - ), - controller: _refreshController, - child: CustomScrollView( - shrinkWrap: true, - controller: _scrollController, - reverse: true, - physics: const ClampingScrollPhysics(), - // 禁用回弹效果 - slivers: [ - SliverList( - delegate: SliverChildBuilderDelegate( - (c, i) => _MessageItem( - message: currentConversationMessageList[i], - preMessage: - i < currentConversationMessageList.length - 1 - ? currentConversationMessageList[i + 1] - : null, - isSystem: - currentConversationMessageList[i].sender == "administrator", - friend: friend, - currentConversationMessageList: currentConversationMessageList, - updateCall: () { - setState(() {}); - }, - ), - childCount: currentConversationMessageList.length, + return LayoutBuilder( + builder: (context, constraints) { + return SizedBox( + width: constraints.maxWidth, + height: constraints.maxHeight, + child: SmartRefresher( + enablePullDown: false, + enablePullUp: true, + onLoading: () async { + print('onLoading'); + if (currentConversationMessageList.isNotEmpty) { + // 拉取单聊历史消息 + // 首次拉取,lastMsgID 设置为 null + // 再次拉取时,lastMsgID 可以使用返回的消息列表中的最后一条消息的id + V2TimValueCallback> v2timValueCallback = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .getC2CHistoryMessageList( + userID: currentConversation!.userID ?? "", + count: 30, + lastMsgID: currentConversationMessageList.last.msgID, + ); + List messages = + v2timValueCallback.data as List; + print('加载前:${currentConversationMessageList.length}'); + currentConversationMessageList.addAll(messages); + print('加载后:${currentConversationMessageList.length}'); + if (messages.length == 30) { + _refreshController.loadComplete(); + } else { + _refreshController.loadNoData(); + } + } else { + _refreshController.loadNoData(); + } + setState(() {}); + }, + footer: CustomFooter( + height: 1, + builder: (context, mode) { + return SizedBox( + height: 1, + width: 1, + child: SizedBox(height: 1, width: 1), + ); + }, + ), + controller: _refreshController, + child: CustomScrollView( + controller: _scrollController, + reverse: true, + physics: const ClampingScrollPhysics(), + // 禁用回弹效果 + slivers: [ + SliverFillRemaining( + hasScrollBody: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: List.generate( + currentConversationMessageList.length, + (index) { + int messageIndex = + currentConversationMessageList.length - 1 - index; + return _MessageItem( + message: currentConversationMessageList[messageIndex], + preMessage: + messageIndex < + currentConversationMessageList.length - 1 + ? currentConversationMessageList[messageIndex + + 1] + : null, + isSystem: + currentConversationMessageList[messageIndex] + .sender == + "administrator", + friend: friend, + currentConversationMessageList: + currentConversationMessageList, + updateCall: () { + setState(() {}); + }, + ); + }, + ), + ), + ), + ], ), ), - ], - ), + ); + }, ); } @@ -486,55 +558,69 @@ class _ATMessageChatPageState extends State { ), ), ), - //原来的emoji按钮 - //SizedBox(width: 10.w,), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + ATKeybordUtil.conceal(context); + setState(() { + if (showTools) { + showTools = false; + } + showEmoji = !showEmoji; + }); + }, + child: Padding( + padding: EdgeInsets.only(left: 8.w, right: 10.w), + child: Image.asset( + showEmoji + ? _strategy + .getATMessageChatPageChatKeyboardIcon() + : _strategy.getATMessageChatPageEmojiIcon(), + width: 24.w, + fit: BoxFit.fill, + ), + ), + ), ], ), ), ), - SizedBox(width: 10.w), - !showEmoji - ? GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - ATKeybordUtil.conceal(context); - setState(() { - if (showTools) { - showTools = false; - } - showEmoji = !showEmoji; - }); - }, - child: Image.asset( - _strategy.getATMessageChatPageEmojiIcon(), - width: 24.w, - //color: Colors.black, - fit: BoxFit.fill, - ), - ) - : Container(), - showEmoji - ? GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - ATKeybordUtil.conceal(context); - setState(() { - if (showTools) { - showTools = false; - } - showEmoji = !showEmoji; - }); - }, - child: Image.asset( - _strategy.getATMessageChatPageChatKeyboardIcon(), - width: 24.w, - //color: Colors.black, - fit: BoxFit.fill, - ), - ) - : Container(), if (!showSend) SizedBox(width: 12.w), if (showSend) SizedBox(width: 5.w), + Visibility( + visible: !showSend, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + ATKeybordUtil.conceal(context); + setState(() { + showEmoji = false; + showTools = false; + }); + SmartDialog.show( + tag: "showGiftControl", + alignment: Alignment.bottomCenter, + maskColor: Colors.transparent, + animationType: SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return GiftPage( + toUser: friend, + isChatGift: true, + onChatGiftSend: _onChatGiftSend, + ); + }, + ); + }, + child: Image.asset( + "atu_images/room/at_icon_botton_gift.png", + width: 26.w, + height: 26.w, + fit: BoxFit.fill, + ), + ), + ), + if (!showSend) SizedBox(width: 8.w), Visibility( visible: !showSend, child: GestureDetector( @@ -608,6 +694,19 @@ class _ATMessageChatPageState extends State { ).sendC2CCustomMsg(msg, currentConversation!, extension); } + void _onChatGiftSend(ChatVibeGiftRes gift, int number) async { + if (!canSendMsg) { + ATTts.show(ATAppLocalizations.of(context)!.canSendMsgTips); + return; + } + SmartDialog.dismiss(tag: "showGiftControl"); + await Provider.of(context, listen: false).sendPrivateGift( + userID: currentConversation?.userID ?? "", + gift: gift, + number: number, + ); + } + ///工具栏 Widget _tools(RtmProvider provider) { return Visibility( @@ -648,16 +747,7 @@ class _ATMessageChatPageState extends State { ATTts.show(ATAppLocalizations.of(context)!.canSendMsgTips); return; } - final List? result = - await ImagePick.pickPicAndVideoFromGallery(context); - - if (result == null) return; // 用户取消选择 - if (result.isNotEmpty) { - provider.sendImageMsg( - selectedList: result, - conversation: currentConversation!, - ); - } + _showAlbumPickerDialog(provider); }, ), ), @@ -669,6 +759,79 @@ class _ATMessageChatPageState extends State { ); } + void _showAlbumPickerDialog(RtmProvider provider) { + SmartDialog.show( + tag: "showAlbumPickerDialog", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return SafeArea( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8.w), + topRight: Radius.circular(8.w), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _albumPickerItem( + ATAppLocalizations.of(context)!.image, + () async { + SmartDialog.dismiss(tag: "showAlbumPickerDialog"); + File? result = await ImagePick.pickSingleFromGallery( + context, + ); + if (result != null) { + provider.sendImageMsg( + selectedList: [result], + conversation: currentConversation!, + ); + } + }, + ), + _albumPickerItem( + ATAppLocalizations.of(context)!.video, + () async { + SmartDialog.dismiss(tag: "showAlbumPickerDialog"); + File? result = await ImagePick.pickVideoFromGallery( + context, + ); + if (result != null) { + provider.sendImageMsg( + selectedList: [result], + conversation: currentConversation!, + ); + } + }, + ), + _albumPickerItem(ATAppLocalizations.of(context)!.cancel, () { + SmartDialog.dismiss(tag: "showAlbumPickerDialog"); + }), + ], + ), + ), + ); + }, + ); + } + + Widget _albumPickerItem(String title, Function() onTap) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: Container( + alignment: Alignment.center, + width: ScreenUtil().screenWidth, + padding: EdgeInsets.symmetric(vertical: 15.w), + child: text(title, fontSize: 15.sp, textColor: Colors.black), + ), + ); + } + _emoji() { return Visibility( visible: showEmoji, @@ -781,6 +944,7 @@ class _ATMessageChatPageState extends State { } else { currentConversationMessageList.insert(0, message); } + _playPrivateGiftAnimation(_privateGiftDataFromMessage(message)); await TencentImSDKPlugin.v2TIMManager .getMessageManager() .markC2CMessageAsRead(userID: currentConversation!.userID!); @@ -924,6 +1088,11 @@ class _ATMessageChatPageState extends State { } class _MessageItem extends StatelessWidget { + static final Map + _chatBoxImageProviderCache = {}; + static const int _maxChatBoxImageProviderCacheSize = 64; + static const Duration _chatBoxImageCacheMaxAge = Duration(days: 7); + final V2TimMessage message; final V2TimMessage? preMessage; @@ -1329,9 +1498,8 @@ class _MessageItem extends StatelessWidget { V2TimCustomElem customElem = message.customElem!; content = customElem.data ?? ""; if (customElem.extension == 'Red_Envelopes') { - ATUserRedPacketSendRes redPacketSendRes = ATUserRedPacketSendRes.fromJson( - jsonDecode(content), - ); + ATUserRedPacketSendRes redPacketSendRes = + ATUserRedPacketSendRes.fromJson(jsonDecode(content)); String packetId = redPacketSendRes.packetId ?? ""; // 获取或创建缓存 Future Future getRedPacketFuture() { @@ -1540,6 +1708,12 @@ class _MessageItem extends StatelessWidget { ); }, ); + } else if (_isPrivateGiftMessage(customElem)) { + Map? giftData = _privateGiftData(customElem.data); + if (giftData != null) { + return _privateGift(giftData); + } + content = ATAppLocalizations.of(context)!.gift2; } else { content = ATAppLocalizations.of(context)!.receivedAMessage; } @@ -1547,11 +1721,155 @@ class _MessageItem extends StatelessWidget { return Builder( builder: (ct) { + String chatBubbleUrl = _chatBubbleUrl().trim(); + bool hasChatBubble = chatBubbleUrl.isNotEmpty; + Widget contentWidget = Container( + constraints: BoxConstraints(maxWidth: width(220)), + child: ExtendedText( + content, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle( + fontSize: sp(14), + color: hasChatBubble ? Colors.white : const Color(0xffB1B1B1), + ), + ), + ); return GestureDetector( + child: + hasChatBubble + ? Container( + constraints: BoxConstraints(maxWidth: width(220)), + padding: EdgeInsets.symmetric(horizontal: 10.w), + child: NinePatchImage( + scale: 3, + alignment: Alignment.center, + imageProvider: _getChatBoxImageProvider(chatBubbleUrl), + sliceCachedKey: _buildNinePatchCacheKey(chatBubbleUrl), + child: contentWidget, + ), + ) + : Container( + decoration: BoxDecoration( + color: const Color(0xffF2F2F2), + borderRadius: BorderRadius.only( + topLeft: + message.isSelf! + ? Radius.circular(8) + : Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: + message.isSelf! + ? Radius.circular(0) + : Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: contentWidget, + ), + onLongPress: () { + _showMsgItemMenu(ct, content); + }, + ); + }, + ); + } + + String _chatBubbleUrl() { + if (isSystem || + message.sender == "administrator" || + message.sender == "customer") { + return ""; + } + String messageChatBubbleUrl = _chatBubbleUrlFromCloudCustomData( + message.cloudCustomData, + ); + if (messageChatBubbleUrl.isNotEmpty) { + return messageChatBubbleUrl; + } + PropsResources? chatBox = + (message.isSelf ?? false) + ? AccountStorage().getChatbox() + : friend?.getChatBox(); + return (chatBox?.expand ?? "").trim(); + } + + String _chatBubbleUrlFromCloudCustomData(String? cloudCustomData) { + String data = (cloudCustomData ?? "").trim(); + if (data.isEmpty) { + return ""; + } + try { + dynamic json = jsonDecode(data); + if (json is Map) { + dynamic chatBubble = json["chatBubble"]; + if (chatBubble is Map) { + return "${chatBubble["expand"] ?? ""}".trim(); + } + return "${json["chatBubbleUrl"] ?? ""}".trim(); + } + } catch (_) {} + return ""; + } + + String _buildNinePatchCacheKey(String? url) { + final String normalizedUrl = (url ?? "").trim(); + if (normalizedUrl.isEmpty) { + return ""; + } + return "$normalizedUrl|scale:3|im"; + } + + ExtendedNetworkImageProvider _getChatBoxImageProvider(String? url) { + final String normalizedUrl = (url ?? "").trim(); + final ExtendedNetworkImageProvider? cached = + _chatBoxImageProviderCache[normalizedUrl]; + if (cached != null) { + return cached; + } + if (_chatBoxImageProviderCache.length >= + _maxChatBoxImageProviderCacheSize) { + _chatBoxImageProviderCache.remove(_chatBoxImageProviderCache.keys.first); + } + final ExtendedNetworkImageProvider provider = ExtendedNetworkImageProvider( + normalizedUrl, + cache: true, + cacheMaxAge: _chatBoxImageCacheMaxAge, + ); + _chatBoxImageProviderCache[normalizedUrl] = provider; + return provider; + } + + bool _isPrivateGiftMessage(V2TimCustomElem customElem) { + if (customElem.desc == "PRIVATE_GIFT") { + return true; + } + Map? data = _privateGiftData(customElem.data); + return data?["type"] == "PRIVATE_GIFT"; + } + + Map? _privateGiftData(String? data) { + return _privateGiftDataFromJson(data); + } + + Widget _privateGift(Map giftData) { + String giftName = "${giftData["giftName"] ?? ""}"; + String giftPhoto = "${giftData["giftPhoto"] ?? ""}"; + String quantity = "${giftData["quantity"] ?? 1}"; + + return Builder( + builder: (ct) { + return GestureDetector( + onTap: () { + _playPrivateGiftAnimation(giftData); + }, + onLongPress: () { + _showMsgItemMenu(ct, giftName); + }, child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 9.w), decoration: BoxDecoration( - color: Colors.white, - boxShadow: [BoxShadow(blurRadius: 2.w, color: Colors.black26)], + color: const Color(0xffF2F2F2), borderRadius: BorderRadius.only( topLeft: message.isSelf! ? Radius.circular(8) : Radius.circular(0), @@ -1561,22 +1879,35 @@ class _MessageItem extends StatelessWidget { message.isSelf! ? Radius.circular(0) : Radius.circular(8), ), ), - padding: EdgeInsets.all(8.0.w), - child: Container( - constraints: BoxConstraints(maxWidth: width(220)), - child: ExtendedText( - content, - // specialTextSpanBuilder: MySpecialTextSpanBuilder(), - style: TextStyle( - fontSize: sp(14), - color: message.isSelf! ? Colors.black87 : Colors.black87, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.send, + fontSize: 11.sp, + textColor: const Color(0xffB1B1B1), ), - ), + SizedBox(width: 7.w), + Container( + width: 31.w, + height: 31.w, + alignment: Alignment.center, + child: netImage( + url: giftPhoto, + width: 31.w, + height: 31.w, + fit: BoxFit.contain, + ), + ), + SizedBox(width: 7.w), + text( + "*$quantity", + fontSize: 11.sp, + textColor: const Color(0xffB1B1B1), + ), + ], ), ), - onLongPress: () { - _showMsgItemMenu(ct, content); - }, ); }, ); diff --git a/lib/chatvibe_features/gift/gift_page.dart b/lib/chatvibe_features/gift/gift_page.dart index ed85530..69a0891 100644 --- a/lib/chatvibe_features/gift/gift_page.dart +++ b/lib/chatvibe_features/gift/gift_page.dart @@ -1,9 +1,7 @@ import 'dart:convert'; import 'dart:ui' as ui; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_debouncer/flutter_debouncer.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -24,8 +22,6 @@ import 'package:provider/provider.dart'; import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; import 'package:aslan/chatvibe_core/constants/at_screen.dart'; import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; -import 'package:aslan/chatvibe_core/utilities/at_dialog_utils.dart'; -import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; import 'package:aslan/chatvibe_data/sources/local/floating_screen_manager.dart'; import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; import 'package:aslan/chatvibe_domain/models/res/mic_res.dart'; @@ -34,11 +30,9 @@ import 'package:aslan/chatvibe_managers/gift_system_manager.dart'; import 'package:aslan/chatvibe_managers/rtc_manager.dart'; import 'package:aslan/chatvibe_managers/rtm_manager.dart'; import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; -import 'package:aslan/chatvibe_ui/widgets/countdown_timer.dart'; import 'package:aslan/chatvibe_ui/widgets/room/give_room_luck_page.dart'; import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; import 'package:aslan/chatvibe_features/wallet/wallet_route.dart'; -import 'package:aslan/chatvibe_features/gift/activity/gift_tab_activity_page.dart'; import 'package:aslan/chatvibe_features/gift/gift_tab_page.dart'; import '../../chatvibe_data/models/enum/at_gift_type.dart'; import '../../chatvibe_data/models/message/at_floating_message.dart'; @@ -47,8 +41,16 @@ import '../../chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart'; class GiftPage extends StatefulWidget { ChatVibeUserProfile? toUser; final int initialIndex; + final bool isChatGift; + final void Function(ChatVibeGiftRes gift, int number)? onChatGiftSend; - GiftPage({super.key, this.toUser, this.initialIndex = 0}); + GiftPage({ + super.key, + this.toUser, + this.initialIndex = 0, + this.isChatGift = false, + this.onChatGiftSend, + }); @override _GiftPageState createState() => _GiftPageState(); @@ -56,15 +58,13 @@ class GiftPage extends StatefulWidget { class _GiftPageState extends State with SingleTickerProviderStateMixin { - late TabController _tabController; + TabController? _tabController; /// 业务逻辑策略访问器 BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; // int checkedIndex = 0; ChatVibeGiftRes? checkedGift; - final List _pages = []; - final List _tabs = []; RtcProvider? rtcProvider; bool isAll = false; @@ -94,144 +94,6 @@ class _GiftPageState extends State // Provider.of(context, listen: false).giftActivityList(); // Provider.of(context, listen: false).giftBackpack(); Provider.of(context, listen: false).balance(); - _pages.add( - GiftTabPage("ALL", (int checkedI) { - checkedGift = null; - var all = - Provider.of( - context, - listen: false, - ).giftByTab["ALL"]; - if (all != null) { - checkedGift = all[checkedI]; - } - number = 1; - noShowNumber = false; - setState(() { - giftType = 0; - }); - }), - ); - // _pages.add( - // GiftTabPage("NATIONAL_FLAG", (int checkedI) { - // checkedGift = null; - // var flag = - // Provider.of( - // context, - // listen: false, - // ).giftByTab["NATIONAL_FLAG"]; - // if (flag != null) { - // checkedGift = flag[checkedI]; - // } - // number = 1; - // noShowNumber = false; - // setState(() { - // giftType = 0; - // }); - // }), - // ); - - // _pages.add( - // GiftTabActivityPage("ACTIVITY", (int checkedI, String activityId) { - // checkedGift = null; - // var exclusive = - // Provider.of( - // context, - // listen: false, - // ).activityGiftByTab[activityId]; - // if (exclusive != null) { - // checkedGift = exclusive[checkedI]; - // } - // number = 1; - // noShowNumber = false; - // setState(() { - // giftType = 1; - // }); - // }), - // ); - - _pages.add( - GiftTabPage("LUCKY_GIFT", (int checkedI) { - checkedGift = null; - var exclusive = - Provider.of( - context, - listen: false, - ).giftByTab["LUCKY_GIFT"]; - if (exclusive != null) { - checkedGift = exclusive[checkedI]; - } - number = 1; - noShowNumber = false; - setState(() { - giftType = 2; - }); - }), - ); - - _pages.add( - GiftTabPage("CP", (int checkedI) { - checkedGift = null; - var exclusive = - Provider.of( - context, - listen: false, - ).giftByTab["CP"]; - if (exclusive != null) { - checkedGift = exclusive[checkedI]; - } - number = 1; - noShowNumber = false; - setState(() { - giftType = 3; - }); - }), - ); - - // _pages.add( - // GiftTabPage("CUSTOMIZED", (int checkedI) { - // checkedGift = null; - // var exclusive = - // Provider.of( - // context, - // listen: false, - // ).giftByTab["CUSTOMIZED"]; - // - // if (exclusive != null && exclusive.length > 1) { - // checkedGift = exclusive[checkedI]; - // } - // number = 1; - // noShowNumber = false; - // setState(() { - // giftType = 4; - // }); - // }), - // ); - - // _pages.add( - // GiftTabPage("MAGIC", (int checkedI) { - // checkedGift = null; - // var exclusive = - // Provider.of( - // context, - // listen: false, - // ).giftByTab["MAGIC"]; - // if (exclusive != null) { - // checkedGift = exclusive[checkedI]; - // } - // number = 1; - // noShowNumber = true; - // setState(() { - // giftType = 5; - // }); - // }), - // ); - _tabController = TabController( - length: _pages.length, - vsync: this, - initialIndex: widget.initialIndex, - ); - _tabController.addListener(() {}); // 监听切换 rtcProvider?.roomWheatMap.forEach((k, v) { if (v.user != null) { if (v.user?.id == AccountStorage().getCurrentUser()?.userProfile?.id) { @@ -249,39 +111,27 @@ class _GiftPageState extends State }); } + @override + void dispose() { + _tabController?.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - _tabs.clear(); - _tabs.add(Tab(text: ATAppLocalizations.of(context)!.gift)); - // _tabs.add(Tab(text: ATAppLocalizations.of(context)!.country)); - // _tabs.add(Tab(text: ATAppLocalizations.of(context)!.activity)); - _tabs.add( - Tab( - child: Directionality(textDirection: TextDirection.ltr, child: Stack( - clipBehavior: Clip.none, - children: [ - Padding( - padding: EdgeInsets.only(right: 10.w), - child: Text(ATAppLocalizations.of(context)!.luck), - ), - Positioned( - top: -6.w, - right: -3.w, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: _showLuckyGiftHelpDialog, - child: Icon(Icons.help_outline_rounded, size: 12.w), - ), - ), - ], - )), - ), - ); - _tabs.add(Tab(text: ATAppLocalizations.of(context)!.cp)); - // _tabs.add(Tab(text: ATAppLocalizations.of(context)!.customized)); - // _tabs.add(Tab(text: ATAppLocalizations.of(context)!.magic)); return Consumer( builder: (context, ref, child) { + final giftTabTypes = _giftTabTypes(ref); + final tabs = giftTabTypes.map(_buildGiftTab).toList(); + final pages = + giftTabTypes + .map( + (giftTab) => GiftTabPage(giftTab, (int checkedI) { + _selectGift(giftTab, checkedI); + }), + ) + .toList(); + _syncTabController(giftTabTypes.length); return SafeArea( top: false, child: Column( @@ -299,119 +149,129 @@ class _GiftPageState extends State child: Column( children: [ SizedBox(height: 12.w), - Row( - children: [ - SizedBox(width: 8.w), - widget.toUser == null - ? Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: - listMai - .map((e) => _maiHead(e)) - .toList(), + if (!widget.isChatGift) + Row( + children: [ + SizedBox(width: 8.w), + widget.toUser == null + ? Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: + listMai + .map((e) => _maiHead(e)) + .toList(), + ), ), + ) + : Stack( + alignment: AlignmentDirectional.center, + children: [ + Container( + padding: EdgeInsets.all(2.w), + child: netImage( + url: widget.toUser?.userAvatar ?? "", + width: 26.w, + defaultImg: + _strategy + .getMePageDefaultAvatarImage(), + shape: BoxShape.circle, + ), + ), + widget.toUser == null + ? PositionedDirectional( + bottom: 0, + end: 0, + child: Image.asset( + "atu_images/login/at_icon_login_ser_select.png", + width: 10.w, + height: 10.w, + ), + ) + : Container(), + ], ), - ) - : Stack( - alignment: AlignmentDirectional.center, - children: [ - Container( - padding: EdgeInsets.all(2.w), - child: netImage( - url: widget.toUser?.userAvatar ?? "", - width: 26.w, - defaultImg: - _strategy - .getMePageDefaultAvatarImage(), - shape: BoxShape.circle, - ), - ), - widget.toUser == null - ? PositionedDirectional( - bottom: 0, - end: 0, - child: Image.asset( - "atu_images/login/at_icon_login_ser_select.png", - width: 10.w, - height: 10.w, - ), - ) - : Container(), - ], - ), - widget.toUser == null - ? Builder( - builder: (ct) { - return GestureDetector( - child: Image.asset( - isAll - ? "atu_images/room/at_icon_gift_all_en.png" - : "atu_images/room/at_icon_gift_all_no.png", - width: 28.w, - height: 28.w, - ), - onTap: () { - isAll = !isAll; - for (var mai in listMai) { - mai.isSelect = isAll; - } - setState(() {}); - // showGiveTypeDialog(ct); - }, - ); - }, - ) - : Container(), - SizedBox(width: 12.w), - ], - ), + widget.toUser == null + ? Builder( + builder: (ct) { + return GestureDetector( + child: Image.asset( + isAll + ? "atu_images/room/at_icon_gift_all_en.png" + : "atu_images/room/at_icon_gift_all_no.png", + width: 28.w, + height: 28.w, + ), + onTap: () { + isAll = !isAll; + for (var mai in listMai) { + mai.isSelect = isAll; + } + setState(() {}); + // showGiveTypeDialog(ct); + }, + ); + }, + ) + : Container(), + SizedBox(width: 12.w), + ], + ), Row( children: [ SizedBox(width: 5.w), Expanded( child: Container( height: 28.w, - child: TabBar( - tabAlignment: TabAlignment.start, - labelPadding: EdgeInsets.symmetric( - horizontal: 8.w, - ), - labelColor: Color(0xffFFD800), + child: + tabs.isEmpty + ? Container() + : TabBar( + tabAlignment: TabAlignment.start, + labelPadding: EdgeInsets.symmetric( + horizontal: 8.w, + ), + labelColor: Color(0xffFFD800), - indicatorWeight: 0, - isScrollable: true, - indicator: ATFixedWidthTabIndicator( - width: 15.w, - gradient: LinearGradient( - colors: [ - Color(0xffFFA500), - Color(0xffFFD700), - ], - ), - ), - unselectedLabelColor: Colors.white54, - labelStyle: TextStyle(fontSize: 14.sp), - unselectedLabelStyle: TextStyle( - fontSize: 12.sp, - ), - indicatorColor: Colors.transparent, - dividerColor: Colors.transparent, - controller: _tabController, - tabs: _tabs, - ), + indicatorWeight: 0, + isScrollable: true, + indicator: ATFixedWidthTabIndicator( + width: 15.w, + gradient: LinearGradient( + colors: [ + Color(0xffFFA500), + Color(0xffFFD700), + ], + ), + ), + unselectedLabelColor: Colors.white54, + labelStyle: TextStyle(fontSize: 14.sp), + unselectedLabelStyle: TextStyle( + fontSize: 12.sp, + ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: tabs, + ), ), ), SizedBox(width: 5.w), ], ), Expanded( - child: TabBarView( - physics: NeverScrollableScrollPhysics(), - controller: _tabController, - children: _pages, - ), + child: + pages.isEmpty + ? mainEmpty( + textColor: Colors.white54, + msg: ATAppLocalizations.of(context)!.noData, + ) + : TabBarView( + physics: NeverScrollableScrollPhysics(), + controller: _tabController, + children: pages, + ), ), Row( children: [ @@ -544,6 +404,119 @@ class _GiftPageState extends State ); } + List _giftTabTypes(AppGeneralManager ref) { + return ref.giftTabs.where((giftTab) { + return !widget.isChatGift || giftTab != ATGiftType.LUCKY_GIFT.name; + }).toList(); + } + + void _syncTabController(int length) { + if (length == 0) { + _tabController?.dispose(); + _tabController = null; + return; + } + if (_tabController?.length == length) { + return; + } + final oldIndex = _tabController?.index ?? widget.initialIndex; + _tabController?.dispose(); + _tabController = TabController( + length: length, + vsync: this, + initialIndex: oldIndex.clamp(0, length - 1).toInt(), + ); + } + + Tab _buildGiftTab(String giftTab) { + if (giftTab == ATGiftType.LUCKY_GIFT.name) { + return Tab( + child: Directionality( + textDirection: TextDirection.ltr, + child: Stack( + clipBehavior: Clip.none, + children: [ + Padding( + padding: EdgeInsets.only(right: 10.w), + child: Text(_giftTabTitle(giftTab)), + ), + Positioned( + top: -6.w, + right: -3.w, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: _showLuckyGiftHelpDialog, + child: Icon(Icons.help_outline_rounded, size: 12.w), + ), + ), + ], + ), + ), + ); + } + return Tab(text: _giftTabTitle(giftTab)); + } + + String _giftTabTitle(String giftTab) { + switch (giftTab) { + case "ALL": + return ATAppLocalizations.of(context)!.gift; + case "NATIONAL_FLAG": + return ATAppLocalizations.of(context)!.country; + case "ACTIVITY": + return ATAppLocalizations.of(context)!.activity; + case "LUCKY_GIFT": + return ATAppLocalizations.of(context)!.luck; + case "CP": + return ATAppLocalizations.of(context)!.cp; + case "CUSTOMIZED": + case "EXCLUSIVE": + return ATAppLocalizations.of(context)!.customized; + case "MAGIC": + return ATAppLocalizations.of(context)!.magic; + default: + return giftTab.replaceAll("_", " "); + } + } + + void _selectGift(String giftTab, int checkedI) { + checkedGift = null; + final gifts = + Provider.of( + context, + listen: false, + ).giftByTab[giftTab]; + if (gifts != null && checkedI >= 0 && checkedI < gifts.length) { + final gift = gifts[checkedI]; + if (gift.id != "-1000") { + checkedGift = gift; + } + } + number = 1; + noShowNumber = giftTab == ATGiftType.MAGIC.name; + setState(() { + giftType = _giftTypeValue(giftTab); + }); + } + + int _giftTypeValue(String giftTab) { + switch (giftTab) { + case "ACTIVITY": + return 1; + case "LUCKY_GIFT": + return 2; + case "CP": + return 3; + case "CUSTOMIZED": + case "EXCLUSIVE": + return 4; + case "MAGIC": + return 5; + default: + return 0; + } + } + void _showLuckyGiftHelpDialog() { if (giftType != 2) { return; @@ -865,6 +838,14 @@ class _GiftPageState extends State ///赠送礼物 void giveGifts() { + if (widget.isChatGift) { + if (checkedGift == null) { + return; + } + widget.onChatGiftSend?.call(checkedGift!, number); + return; + } + List acceptUserIds = []; List acceptUsers = []; @@ -1055,7 +1036,7 @@ class _GiftPageState extends State } _buildGiftHead() { - if (giftType == 1 || giftType == 5) { + if (giftType == 1 || giftType == 5) { // 获取基础路径 String basePath = _strategy.getGiftPageActivityGiftHeadBackground( _giftTypeToString(giftType), diff --git a/lib/chatvibe_features/gift/gift_tab_page.dart b/lib/chatvibe_features/gift/gift_tab_page.dart index 9109b24..20509d9 100644 --- a/lib/chatvibe_features/gift/gift_tab_page.dart +++ b/lib/chatvibe_features/gift/gift_tab_page.dart @@ -13,8 +13,6 @@ import 'package:aslan/app_localizations.dart'; import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; -import '../../chatvibe_data/models/enum/at_gift_type.dart'; - class GiftTabPage extends StatefulWidget { String type; bool isDark = true; @@ -37,7 +35,7 @@ class _GiftTabPageState extends State { void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - if (widget.type == "CUSTOMIZED") { + if (widget.type == "CUSTOMIZED" || widget.type == "EXCLUSIVE") { widget.checkedCall(1); } else { widget.checkedCall(0); @@ -56,7 +54,7 @@ class _GiftTabPageState extends State { ) : Column( children: [ - SizedBox(height: 23.w,), + SizedBox(height: 23.w), Expanded( child: PageView.builder( controller: _giftPageController, @@ -250,7 +248,7 @@ class _GiftTabPageState extends State { lineHeight: 1, textAlign: TextAlign.center, fontWeight: FontWeight.w600, - textColor: widget.isDark ? Colors.white : Colors.black, + textColor: widget.isDark ? Colors.white : Colors.black, ), ), Row( diff --git a/lib/chatvibe_features/home/medals_honor/medals_honor_tab_page.dart b/lib/chatvibe_features/home/medals_honor/medals_honor_tab_page.dart index 4d863fa..c3650ae 100644 --- a/lib/chatvibe_features/home/medals_honor/medals_honor_tab_page.dart +++ b/lib/chatvibe_features/home/medals_honor/medals_honor_tab_page.dart @@ -149,9 +149,9 @@ class _MedalsHonorTabPageState for (final response in results) { for (final group in response) { for (final item in group.badgeTables ?? const []) { - if (!(item.activation ?? false)) { - continue; - } + // if (!(item.activation ?? false)) { + // continue; + // } final key = item.badgeConfig?.id ?? item.badgeConfig?.badgeKey ?? diff --git a/lib/chatvibe_features/home/popular/mine/home_mine_page.dart b/lib/chatvibe_features/home/popular/mine/home_mine_page.dart index 08c16b2..bef2865 100644 --- a/lib/chatvibe_features/home/popular/mine/home_mine_page.dart +++ b/lib/chatvibe_features/home/popular/mine/home_mine_page.dart @@ -264,8 +264,19 @@ class _HomeMinePageState extends ATPageListState { ), ], ), - onTap: () { - provider.createRoom(context); + onTap: () async { + final myRoom = await provider.createRoom(context); + if (!mounted) { + return; + } + final roomId = myRoom?.id ?? ""; + if (roomId.isEmpty) { + return; + } + Provider.of( + context, + listen: false, + ).joinRoom(context, roomId); }, ); }, diff --git a/lib/chatvibe_features/home/popular/party/home_party_page.dart b/lib/chatvibe_features/home/popular/party/home_party_page.dart index a8a5d1e..f099f93 100644 --- a/lib/chatvibe_features/home/popular/party/home_party_page.dart +++ b/lib/chatvibe_features/home/popular/party/home_party_page.dart @@ -109,16 +109,35 @@ class _HomePartyPageState extends State msg: ATAppLocalizations.of(context)!.noData, ), ) - : ListView.separated( - shrinkWrap: true, - padding: EdgeInsets.symmetric(vertical: 8.w), - physics: const NeverScrollableScrollPhysics(), - itemCount: rooms.length, - itemBuilder: (context, index) { - return _buildItem(rooms[index], index); - }, - separatorBuilder: (BuildContext context, int index) { - return SizedBox(height: 4.w); + : Selector>( + selector: + (_, provider) => + List.unmodifiable( + provider.roomListBanners, + ), + builder: (context, roomListBanners, child) { + return ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.symmetric(vertical: 8.w), + physics: const NeverScrollableScrollPhysics(), + itemCount: _partyRoomListItemCount(roomListBanners), + itemBuilder: (context, index) { + if (_isPartyBannerIndex(index, roomListBanners)) { + return _buildPartyBanner(roomListBanners); + } + final roomIndex = _roomIndexForListIndex( + index, + roomListBanners, + ); + return _buildItem(rooms[roomIndex], roomIndex); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(height: 4.w); + }, + ); }, ), ], @@ -130,6 +149,67 @@ class _HomePartyPageState extends State ); } + bool _shouldShowPartyBanner(List roomListBanners) { + return rooms.length >= 3 && roomListBanners.isNotEmpty; + } + + int _partyRoomListItemCount(List roomListBanners) { + return rooms.length + (_shouldShowPartyBanner(roomListBanners) ? 1 : 0); + } + + bool _isPartyBannerIndex(int index, List roomListBanners) { + return _shouldShowPartyBanner(roomListBanners) && index == 3; + } + + int _roomIndexForListIndex( + int index, + List roomListBanners, + ) { + return _shouldShowPartyBanner(roomListBanners) && index > 3 + ? index - 1 + : index; + } + + Widget _buildPartyBanner(List roomListBanners) { + return SizedBox( + height: 97.w, + child: Container( + margin: EdgeInsetsDirectional.symmetric(horizontal: 17.w), + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(12.w)), + child: CarouselSlider( + options: CarouselOptions( + height: 97.w, + autoPlay: false, + enlargeCenterPage: false, + enableInfiniteScroll: roomListBanners.length > 1, + viewportFraction: 1, + ), + items: + roomListBanners.map((item) { + return _buildPartyBannerRemoteItem(item); + }).toList(), + ), + ), + ); + } + + Widget _buildPartyBannerRemoteItem(ATIndexBannerRes item) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + child: netImage( + url: item.cover ?? "", + width: double.infinity, + height: 97.w, + fit: BoxFit.cover, + borderRadius: BorderRadius.circular(12.w), + ), + onTap: () { + ATBannerUtils.openBanner(item, context); + }, + ); + } + _banner(List exploreBanners) { return Stack( alignment: AlignmentDirectional.bottomCenter, @@ -431,9 +511,10 @@ class _HomePartyPageState extends State _countryDisplayName(country), maxLines: 1, overflow: TextOverflow.ellipsis, - textColor: isSelected - ? const Color(0xff333333) - : const Color(0xff333333), + textColor: + isSelected + ? const Color(0xff333333) + : const Color(0xff333333), fontSize: 14.sp, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, lineHeight: 1.1, diff --git a/lib/chatvibe_features/room/at_voice_chat_room_page.dart b/lib/chatvibe_features/room/at_voice_chat_room_page.dart index 022e20c..f3f0ed5 100644 --- a/lib/chatvibe_features/room/at_voice_chat_room_page.dart +++ b/lib/chatvibe_features/room/at_voice_chat_room_page.dart @@ -160,7 +160,9 @@ class _VoiceRoomPageState extends State ), Consumer( builder: (context, ref, child) { - return ref.hideLGiftAnimal + return ref.hideLGiftAnimal || + ref.hideLGiftAnimalOnRoomMessagePage || + ref.hideLGiftAnimalOnRoomH5Page ? Container() : GiveRoomLuckWithOtherWidget(); }, diff --git a/lib/chatvibe_features/room/detail/room_detail_page.dart b/lib/chatvibe_features/room/detail/room_detail_page.dart index 25d8142..a49041e 100644 --- a/lib/chatvibe_features/room/detail/room_detail_page.dart +++ b/lib/chatvibe_features/room/detail/room_detail_page.dart @@ -159,8 +159,8 @@ class _RoomDetailPageState extends State { ref .currenRoom ?.roomProfile - ?.roomProfile - ?.roomAccount ?? + ?.userProfile + ?.getID() ?? "", ), ); diff --git a/lib/chatvibe_features/room/seat/seat_item.dart b/lib/chatvibe_features/room/seat/seat_item.dart index 1e6a038..a22d8b3 100644 --- a/lib/chatvibe_features/room/seat/seat_item.dart +++ b/lib/chatvibe_features/room/seat/seat_item.dart @@ -179,7 +179,7 @@ class _SeatItemState extends State with TickerProviderStateMixin { children: [ SizedBox(height: 16.w), text( - "NO.${widget.index}", + "NO.${widget.index + 1}", fontSize: 10.sp, fontWeight: FontWeight.w600, ), diff --git a/lib/chatvibe_features/settings/language/language_page.dart b/lib/chatvibe_features/settings/language/language_page.dart index 33fb0fe..e769498 100644 --- a/lib/chatvibe_features/settings/language/language_page.dart +++ b/lib/chatvibe_features/settings/language/language_page.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:aslan/app_localizations.dart'; @@ -6,32 +5,38 @@ import 'package:provider/provider.dart'; import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; -import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; import 'package:aslan/chatvibe_managers/localization_manager.dart'; import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; ///语言选择 class LanguagePage extends StatefulWidget { + const LanguagePage({super.key}); + @override - _LanguagePageState createState() => _LanguagePageState(); + State createState() => _LanguagePageState(); } class _LanguagePageState extends State { - int selectType = 1; + static const List<_LanguageOption> _languageOptions = [ + _LanguageOption("English", "en"), + _LanguageOption("العربية", "ar"), + _LanguageOption("Türkçe", "tr"), + _LanguageOption("বাংলা", "bn"), + _LanguageOption("Bahasa Indonesia", "id"), + _LanguageOption("Filipino", "fil"), + _LanguageOption("اردو", "ur"), + _LanguageOption("हिन्दी", "hi"), + ]; + + String _selectedLanguageCode = "en"; @override void initState() { super.initState(); String langCode = DataPersistence.getLang(); - if (langCode == "ar") { - selectType = 1; - }else if(langCode == "tr"){ - selectType = 2; - }else if(langCode == "bn"){ - selectType = 3; - } else { - selectType = 0; + if (_languageOptions.any((option) => option.languageCode == langCode)) { + _selectedLanguageCode = langCode; } } @@ -46,201 +51,79 @@ class _LanguagePageState extends State { fit: BoxFit.fill, ), Scaffold( - backgroundColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageScaffoldBackgroundColor(), + backgroundColor: + ATGlobalConfig.businessLogicStrategy + .getLanguagePageScaffoldBackgroundColor(), resizeToAvoidBottomInset: false, appBar: ChatVibeStandardAppBar( title: ATAppLocalizations.of(context)!.language, actions: [], ), body: Column( - children: [ - Row( - children: [ - SizedBox(width: 15.w), - Expanded( - child: text( - "English", - textColor: ATGlobalConfig.businessLogicStrategy.getLanguagePagePrimaryTextColor(), - fontSize: 15.sp, - ), - ), - Checkbox( - value: selectType == 0, - checkColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckColor(), - activeColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxActiveColor(), - onChanged: (b) { - if (selectType == 0) { - selectType = 1; - } else { - selectType = 0; - } - setState(() {}); - if (b!) { - Provider.of( - context, - listen: false, - ).setLocale(const Locale('en', '')); - } - }, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderRadius()), - ), - side: WidgetStateBorderSide.resolveWith((states) { - if (states.contains(WidgetState.selected)) { - return BorderSide( - color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), - width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), - ); // 选中时保留蓝色边框 - } - return BorderSide( - color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), - width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), - ); // 未选中时灰色边框 - }), - ), - ], - ), - Row( - children: [ - SizedBox(width: 15.w), - Expanded( - child: text( - "العربية", - textColor: ATGlobalConfig.businessLogicStrategy.getLanguagePagePrimaryTextColor(), - fontSize: 15.sp, - ), - ), - Checkbox( - value: selectType == 1, - checkColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckColor(), - activeColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxActiveColor(), - onChanged: (b) { - if (selectType == 1) { - selectType = 0; - } else { - selectType = 1; - } - setState(() {}); - if (b!) { - Provider.of( - context, - listen: false, - ).setLocale(const Locale('ar', '')); - } - }, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderRadius()), - ), - side: WidgetStateBorderSide.resolveWith((states) { - if (states.contains(WidgetState.selected)) { - return BorderSide( - color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), - width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), - ); // 选中时保留蓝色边框 - } - return BorderSide( - color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), - width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), - ); // 未选中时灰色边框 - }), - ), - ], - ), - Row( - children: [ - SizedBox(width: 15.w), - Expanded( - child: text( - "Türkçe", - textColor: ATGlobalConfig.businessLogicStrategy.getLanguagePagePrimaryTextColor(), - fontSize: 15.sp, - ), - ), - Checkbox( - value: selectType == 2, - checkColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckColor(), - activeColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxActiveColor(), - onChanged: (b) { - if (selectType == 2) { - selectType = 0; - } else { - selectType = 2; - } - setState(() {}); - if (b!) { - Provider.of( - context, - listen: false, - ).setLocale(const Locale('tr', '')); - } - }, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderRadius()), - ), - side: WidgetStateBorderSide.resolveWith((states) { - if (states.contains(WidgetState.selected)) { - return BorderSide( - color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), - width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), - ); // 选中时保留蓝色边框 - } - return BorderSide( - color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), - width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), - ); // 未选中时灰色边框 - }), - ), - ], - ), Row( - children: [ - SizedBox(width: 15.w), - Expanded( - child: text( - "বাংলা", - textColor: ATGlobalConfig.businessLogicStrategy.getLanguagePagePrimaryTextColor(), - fontSize: 15.sp, - ), - ), - Checkbox( - value: selectType == 3, - checkColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckColor(), - activeColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxActiveColor(), - onChanged: (b) { - if (selectType == 3) { - selectType = 0; - } else { - selectType = 3; - } - setState(() {}); - if (b!) { - Provider.of( - context, - listen: false, - ).setLocale(const Locale('bn', '')); - } - }, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderRadius()), - ), - side: WidgetStateBorderSide.resolveWith((states) { - if (states.contains(WidgetState.selected)) { - return BorderSide( - color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), - width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), - ); // 选中时保留蓝色边框 - } - return BorderSide( - color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), - width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), - ); // 未选中时灰色边框 - }), - ), - ], - ), - ], + children: _languageOptions.map(_buildLanguageRow).toList(), ), ), ], ); } + + Widget _buildLanguageRow(_LanguageOption option) { + return Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: text( + option.title, + textColor: + ATGlobalConfig.businessLogicStrategy + .getLanguagePagePrimaryTextColor(), + fontSize: 15.sp, + ), + ), + Checkbox( + value: _selectedLanguageCode == option.languageCode, + checkColor: + ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckColor(), + activeColor: + ATGlobalConfig.businessLogicStrategy + .getLanguagePageCheckboxActiveColor(), + onChanged: (b) { + if (b != true) { + return; + } + setState(() { + _selectedLanguageCode = option.languageCode; + }); + Provider.of( + context, + listen: false, + ).setLocale(Locale(option.languageCode, '')); + }, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + ATGlobalConfig.businessLogicStrategy + .getLanguagePageCheckboxBorderRadius(), + ), + ), + side: WidgetStateBorderSide.resolveWith((states) { + return BorderSide( + color: + ATGlobalConfig.businessLogicStrategy + .getLanguagePageCheckboxBorderColor(), + width: + ATGlobalConfig.businessLogicStrategy + .getLanguagePageCheckboxBorderWidth(), + ); + }), + ), + ], + ); + } +} + +class _LanguageOption { + final String title; + final String languageCode; + + const _LanguageOption(this.title, this.languageCode); } diff --git a/lib/chatvibe_features/user/profile/person_detail_page.dart b/lib/chatvibe_features/user/profile/person_detail_page.dart index 4c9b4ca..89f04d8 100644 --- a/lib/chatvibe_features/user/profile/person_detail_page.dart +++ b/lib/chatvibe_features/user/profile/person_detail_page.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:ui'; import 'package:aslan/chatvibe_features/user/profile/relation/relation_ship_page.dart'; import 'package:carousel_slider/carousel_slider.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -31,6 +30,7 @@ import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; import 'package:aslan/chatvibe_features/chat/at_chat_route.dart'; @@ -43,11 +43,10 @@ class PersonDetailPage extends StatefulWidget { final String isMe; final String tageId; - const PersonDetailPage({Key? key, required this.isMe, required this.tageId}) - : super(key: key); + const PersonDetailPage({super.key, required this.isMe, required this.tageId}); @override - _PersonDetailPageState createState() => _PersonDetailPageState(); + State createState() => _PersonDetailPageState(); } class _PersonDetailPageState extends State @@ -67,6 +66,7 @@ class _PersonDetailPageState extends State // 添加滚动控制器 final ScrollController _scrollController = ScrollController(); double _opacity = 0.0; + double _scrollOffset = 0.0; @override void initState() { @@ -92,9 +92,10 @@ class _PersonDetailPageState extends State // 当滚动到一定位置时,头像和昵称应该完全显示在AppBar上 // 这里计算透明度,可以根据需要调整 final newOpacity = (offset / 320).clamp(0.0, 1.0); - if (newOpacity != _opacity) { + if (newOpacity != _opacity || offset != _scrollOffset) { setState(() { _opacity = newOpacity; + _scrollOffset = offset; }); } } @@ -126,6 +127,7 @@ class _PersonDetailPageState extends State AccountRepository() .blacklistCheck(widget.tageId) .then((v) { + if (!mounted) return; isBlacklist = v; isBlacklistLoading = false; setState(() {}); @@ -159,1164 +161,63 @@ class _PersonDetailPageState extends State } Widget _buildTab(int index, String text) { - final isSelected = _tabController.index == index; return Tab(text: text); } @override Widget build(BuildContext context) { _tabs.clear(); - // _tabs.add(_buildTab(0, ATAppLocalizations.of(context)!.dynamicT)); _tabs.add(_buildTab(0, ATAppLocalizations.of(context)!.aboutMe)); _tabs.add(_buildTab(1, ATAppLocalizations.of(context)!.giftwall)); - _tabs.add(_buildTab(3, ATAppLocalizations.of(context)!.relationShip)); + _tabs.add(_buildTab(2, ATAppLocalizations.of(context)!.relationShip)); + return Consumer( builder: (context, ref, child) { - List backgroundPhotos = []; - backgroundPhotos = + final backgroundPhotos = (ref.userProfile?.backgroundPhotos ?? []) .where((t) => t.status == 1) .toList(); + final hideProfile = isBlacklistLoading || isBlacklist; + return Directionality( textDirection: - window.locale.languageCode == "ar" + PlatformDispatcher.instance.locale.languageCode == "ar" ? TextDirection.rtl : TextDirection.ltr, child: SafeArea( top: false, - child: Stack( - children: [ - // Container( - // width: ScreenUtil().screenWidth, - // height: ScreenUtil().screenHeight, - // color: Color(0xffFDF7E5), - // ), - backgroundPhotos.isNotEmpty - ? (isBlacklistLoading || isBlacklist - ? Container() - : Column( - children: [ - SizedBox( - height: 300.w, - child: CarouselSlider( - options: CarouselOptions( - height: 300.w, - autoPlay: backgroundPhotos.length > 1, - // 启用自动播放 - enlargeCenterPage: false, - // 居中放大当前页面 - aspectRatio: 1 / 1, - // 宽高比 - enableInfiniteScroll: true, - // 启用无限循环 - autoPlayAnimationDuration: Duration( - milliseconds: 800, - ), - // 自动播放动画时长 - viewportFraction: 1, - // 视口分数 - onPageChanged: (index, reason) { - setState(() {}); - }, - ), - items: - backgroundPhotos.map((item) { - return GestureDetector( - child: netImage( - url: item.url ?? "", - width: ScreenUtil().screenWidth, - height: 300.w, - fit: BoxFit.cover, - ), - onTap: () {}, - ); - }).toList(), - ), - ), - Transform.translate( - offset: Offset(0, -6.w), - child: Container( - decoration: BoxDecoration( - color: Color(0xffFDF7E5), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(8.w), - topRight: Radius.circular(8.w), - ), - ), - width: ScreenUtil().screenWidth, - height: ScreenUtil().screenHeight, - ), - ), - ], - )) - : Column( - children: [ - SizedBox( - height: 300.w, - child: Image.asset( - 'atu_images/person/at_icon_my_head_bg_defalt.png', - width: ScreenUtil().screenWidth, - height: 300.w, - fit: BoxFit.cover, - ), - ), - Transform.translate( - offset: Offset(0, -6.w), - child: Container( - decoration: BoxDecoration( - color: Color(0xffFDF7E5), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(8.w), - topRight: Radius.circular(8.w), - ), - ), - width: ScreenUtil().screenWidth, - height: ScreenUtil().screenHeight, - ), - ), - ], - ), - Scaffold( - resizeToAvoidBottomInset: false, - backgroundColor: Colors.transparent, - appBar: ChatVibeStandardAppBar( - backButtonColor: Colors.white, - title: "", - leading: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - Navigator.pop(context); + child: Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: const Color(0xffFDF7E5), + body: Stack( + children: [ + if (!hideProfile) _buildTopBackground(backgroundPhotos), + if (!hideProfile) + ExtendedNestedScrollView( + controller: _scrollController, + onlyOneScrollInBody: true, + pinnedHeaderSliverHeightBuilder: () { + return ScreenUtil().statusBarHeight + kToolbarHeight; }, - child: Container( - width: width(50), - height: height(30), - alignment: AlignmentDirectional.centerStart, - padding: EdgeInsetsDirectional.only(start: width(15)), - child: Icon( - window.locale.languageCode == "ar" - ? Icons.keyboard_arrow_right - : Icons.keyboard_arrow_left, - size: 28.w, - color: Colors.white, - ), - ), - ), - actions: [ - // 在AppBar中显示头像和昵称 - if (_opacity > 0.5) ...[ - SizedBox(width: 45.w), - head( - url: ref.userProfile?.userAvatar ?? "", - width: 50.w, - height: 50.w, - headdress: ref.userProfile?.getHeaddress()?.sourceUrl, - ), - SizedBox(width: 8.w), - chatvibeNickNameText( - maxWidth: 135.w, - ref.userProfile?.userNickname ?? "", - fontSize: 16.sp, - textColor: Color(0xFF333333), - fontWeight: FontWeight.w600, - type: ref.userProfile?.getVIP()?.name ?? "", - needScroll: - (ref - .userProfile - ?.userNickname - ?.characters - .length ?? - 0) > - 13, - ), - SizedBox(width: 8.w), - ], - Spacer(), - Builder( - builder: (ct) { - return IconButton( - icon: - widget.isMe == "true" - ? Image.asset( - "atu_images/person/at_icon_edit_user_info2.png", - width: 22.w, - color: Colors.white, - ) - : Icon( - Icons.more_horiz, - size: 22.w, - color: Colors.white, - ), - onPressed: () { - if (widget.isMe == "true") { - ATNavigatorUtils.push( - context, - MainRoute.edit, - replace: false, - ); - return; - } - SmartDialog.showAttach( - tag: "showUserInfoOptMenu", - targetContext: ct, - alignment: Alignment.bottomCenter, - maskColor: Colors.transparent, - animationType: SmartAnimationType.fade, - scalePointBuilder: - (selfSize) => Offset(selfSize.width, 10), - builder: (_) { - return GestureDetector( - child: Transform.translate( - offset: Offset(0, -10), - child: buildOptUserMenu(context, ref), - ), - onTap: () { - SmartDialog.dismiss( - tag: "showUserInfoOptMenu", - ); - }, - ); - }, - ); - }, - ); - }, - ), - ], - ), - body: - isBlacklistLoading || isBlacklist - ? Container() - : ExtendedNestedScrollView( - controller: _scrollController, - onlyOneScrollInBody: true, - headerSliverBuilder: ( - BuildContext context, - bool innerBoxIsScrolled, - ) { - return [ - SliverToBoxAdapter( - child: Container( - color: Colors.transparent, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SizedBox(height: 210.w), // 给AppBar留出空间 - Column( - children: [ - Row( - children: [ - SizedBox(width: 15.w), - (ref - .userProfile - ?.cpList - ?.isNotEmpty ?? - false) - ? SizedBox( - height: 100.w, - child: Stack( - alignment: - Alignment.center, - children: [ - Row( - mainAxisSize: - MainAxisSize - .min, - crossAxisAlignment: - CrossAxisAlignment - .center, - children: [ - GestureDetector( - child: head( - url: - ref - .userProfile - ?.cpList - ?.first - .meUserAvatar ?? - "", - width: 88.w, - headdress: - ref.userProfile - ?.getHeaddress() - ?.sourceUrl, - ), - onTap: () { - String - encodedUrls = Uri.encodeComponent( - jsonEncode([ - ref - .userProfile - ?.cpList - ?.first - .meUserAvatar, - ]), - ); - ATNavigatorUtils.push( - context, - "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0", - ); - }, - ), - SizedBox( - width: 25.w, - ), - GestureDetector( - onTap: () { - ATNavigatorUtils.push( - context, - replace: - true, - "${MainRoute.person}?isMe=${UserManager().getCurrentUser()?.userProfile?.id == ref.userProfile?.cpList?.first.cpUserId}&tageId=${ref.userProfile?.cpList?.first.cpUserId}", - ); - }, - child: head( - url: - ref - .userProfile - ?.cpList - ?.first - .cpUserAvatar ?? - "", - headdress: - ref - .userProfile - ?.cpList - ?.first - .cpUserAvatarFrame, - width: 88.w, - ), - ), - ], - ), - IgnorePointer( - child: netImage( - url: - ref - .userProfile - ?.cpList - ?.first - .selfRing - ?.sourceUrl ?? - "", - height: 75.w, - width: 75.w, - noDefaultImg: - false, - ), - ), - ], - ), - ) - : GestureDetector( - child: head( - url: - ref - .userProfile - ?.userAvatar ?? - "", - width: 88.w, - headdress: - ref.userProfile - ?.getHeaddress() - ?.sourceUrl, - ), - onTap: () { - String encodedUrls = - Uri.encodeComponent( - jsonEncode([ - ref - .userProfile - ?.userAvatar, - ]), - ); - ATNavigatorUtils.push( - context, - "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0", - ); - }, - ), - ], - ), - SizedBox(height: 8.w), - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Row( - children: [ - SizedBox(width: 12.w), - chatvibeNickNameText( - maxWidth: 135.w, - ref - .userProfile - ?.userNickname ?? - "", - fontSize: 18.sp, - textColor: Color( - 0xFF333333, - ), - fontWeight: - FontWeight.w600, - type: - ref.userProfile - ?.getVIP() - ?.name ?? - "", - needScroll: - (ref - .userProfile - ?.userNickname - ?.characters - .length ?? - 0) > - 13, - ), - SizedBox(width: 3.w), - getVIPBadge( - ref.userProfile - ?.getVIP() - ?.name, - width: 65.w, - height: 28.w, - ), - SizedBox(width: 3.w), - Container( - width: - (ref.userProfile?.age ?? - 0) > - 999 - ? 56.w - : 46.w, - height: 23.w, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage( - ref.userProfile?.userSex == - 0 - ? "atu_images/login/at_icon_sex_woman_bg.png" - : "atu_images/login/at_icon_sex_man_bg.png", - ), - ), - ), - child: Row( - crossAxisAlignment: - CrossAxisAlignment - .center, - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - xb( - ref - .userProfile - ?.userSex, - ), - text( - "${ref.userProfile?.age ?? 0}", - textColor: - Colors.white, - fontSize: 13.sp, - fontWeight: - FontWeight.w600, - ), - SizedBox(width: 3.w), - ], - ), - ), - ], - ), - SingleChildScrollView( - scrollDirection: - Axis.horizontal, - child: Row( - children: [ - SizedBox(width: 12.w), - Row( - children: [ - Row( - children: [ - getIdIcon( - ref - .userProfile - ?.wealthLevel ?? - 0, - width: 32.w, - height: 32.w, - fontSize: 16.sp, - fontWeight: - FontWeight - .w600, - textColor: Color( - 0xFF333333, - ), - ), - chatvibeNickNameText( - maxWidth: 135.w, - ref.userProfile - ?.getID() ?? - "", - textColor: Color( - 0xFF333333, - ), - fontSize: 16.sp, - fontWeight: - FontWeight - .w600, - type: - ref.userProfile - ?.getVIP() - ?.name ?? - "", - needScroll: - (ref.userProfile - ?.getID() - .characters - .length ?? - 0) > - 13, - ), - ], - ), - ], - ), - SizedBox(width: 3.w), - getWealthLevel( - ref - .userProfile - ?.wealthLevel ?? - 0, - width: 58.w, - height: 30.w, - ), - SizedBox(width: 3.w), - getUserLevel( - ref - .userProfile - ?.charmLevel ?? - 0, - width: 58.w, - height: 30.w, - ), - SizedBox(width: 5.w), - ], - ), - ), - ], - ), - ref.userProfile?.wearHonor?.where(( - item, - ) { - return item.use ?? false; - }).isNotEmpty ?? - false - ? Row( - children: [ - SizedBox(width: 15.w), - Expanded( - child: Wrap( - runSpacing: 2.w, - spacing: 4.w, - direction: - Axis.horizontal, - children: - ref - .userProfile! - .wearHonor! - .where((item) { - return item - .use ?? - false; - }) - .map((item) { - return netImage( - url: - item.animationUrl ?? - "", - height: - 28.w, - ); - }) - .toList(), - ), - ), - SizedBox(width: 5.w), - ], - ) - : Container(), - (ref.userProfile?.wearHonor?.where(( - item, - ) { - return item.use ?? false; - }).isNotEmpty ?? - false) - ? SizedBox(height: 5.w) - : Container(), - Container( - margin: - EdgeInsetsDirectional.symmetric( - horizontal: 10.w, - ), - alignment: - AlignmentDirectional - .centerStart, - child: SingleChildScrollView( - scrollDirection: - Axis.horizontal, - child: Row( - mainAxisSize: - MainAxisSize.min, - children: [ - SizedBox(width: 5.w), - ref.userProfile?.wearBadge - ?.where((item) { - return item - .use ?? - false; - }) - .isNotEmpty ?? - false - ? SizedBox( - height: 35.w, - child: ListView.separated( - scrollDirection: - Axis.horizontal, - shrinkWrap: true, - itemCount: - ref - .userProfile - ?.wearBadge - ?.where(( - item, - ) { - return item - .use ?? - false; - }) - .length ?? - 0, - itemBuilder: ( - context, - index, - ) { - return netImage( - width: 35.w, - height: 35.w, - url: - ref - .userProfile - ?.wearBadge - ?.where(( - item, - ) { - return item.use ?? - false; - }) - .toList()[index] - .selectUrl ?? - "", - ); - }, - separatorBuilder: ( - BuildContext - context, - int index, - ) { - return SizedBox( - width: 5.w, - ); - }, - ), - ) - : Container(), - SizedBox(width: 5.w), - ], - ), - ), - ), - SizedBox(height: 5.w), - Consumer< - ChatVibeUserProfileManager - >( - builder: (context, ref, child) { - return Container( - height: 72.w, - alignment: - AlignmentDirectional - .center, - width: - ScreenUtil().screenWidth, - margin: EdgeInsets.symmetric( - horizontal: 18.w, - ), - padding: EdgeInsets.symmetric( - horizontal: 35.w, - ), - child: Row( - mainAxisSize: - MainAxisSize.max, - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - GestureDetector( - child: Column( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - text( - "${counterMap["INTERVIEW"]?.quantity ?? 0}", - fontSize: 17.sp, - textColor: - Colors.black, - fontWeight: - FontWeight - .bold, - ), - text( - ATAppLocalizations.of( - context, - )!.vistors, - fontSize: 14.sp, - textColor: Color( - 0xffB1B1B1, - ), - ), - ], - ), - onTap: () { - if (widget.isMe == - "true") { - ATNavigatorUtils.push( - context, - "${MainRoute.vistors}?removePreviousPersonDetail=true", - ); - } - }, - ), - Container( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Color( - 0xff333333, - ).withOpacity( - 0.0, - ), - Color(0xff333333), - Color( - 0xff333333, - ).withOpacity( - 0.0, - ), - ], - begin: - Alignment - .topCenter, - end: - Alignment - .bottomCenter, - ), - ), - height: 25.w, - width: 1.w, - ), - GestureDetector( - onTap: () { - if (widget.isMe == - "true") { - ATNavigatorUtils.push( - context, - "${MainRoute.follow}?removePreviousPersonDetail=true", - ); - } - }, - child: Column( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - text( - "${counterMap["SUBSCRIPTION"]?.quantity ?? 0}", - fontSize: 17.sp, - textColor: - Colors.black, - fontWeight: - FontWeight - .bold, - ), - text( - ATAppLocalizations.of( - context, - )!.follow, - fontSize: 14.sp, - textColor: Color( - 0xffB1B1B1, - ), - ), - ], - ), - ), - Container( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Color( - 0xff333333, - ).withOpacity( - 0.0, - ), - Color(0xff333333), - Color( - 0xff333333, - ).withOpacity( - 0.0, - ), - ], - begin: - Alignment - .topCenter, - end: - Alignment - .bottomCenter, - ), - ), - height: 25.w, - width: 1.w, - ), - GestureDetector( - child: Column( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - text( - "${counterMap["FANS"]?.quantity ?? 0}", - fontSize: 17.sp, - textColor: - Colors.black, - fontWeight: - FontWeight - .bold, - ), - text( - ATAppLocalizations.of( - context, - )!.fans, - fontSize: 14.sp, - textColor: Color( - 0xffB1B1B1, - ), - ), - ], - ), - onTap: () { - if (widget.isMe == - "true") { - ATNavigatorUtils.push( - context, - "${MainRoute.fans}?removePreviousPersonDetail=true", - ); - } - }, - ), - ], - ), - ); - }, - ), - SizedBox(height: 6.w), - ], - ), - ], - ), - ), - ), - ]; - }, - body: Container( - padding: EdgeInsets.symmetric(horizontal: 3.w), - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(10.w), - topRight: Radius.circular(8.w), - ), - gradient: - ref.userProfile?.userSex == 0 - ? LinearGradient( - colors: [ - Color(0xFFFDF7E5), - Color(0xFFFDF7E5), - ], - begin: AlignmentDirectional.topStart, - end: AlignmentDirectional.bottomEnd, - ) - : LinearGradient( - colors: [ - Color(0xFFFDF7E5), - Color(0xFFFDF7E5), - ], - begin: AlignmentDirectional.topStart, - end: AlignmentDirectional.bottomEnd, - ), - ), - child: Column( - children: [ - Row( - children: [ - SizedBox(width: 6.w), - SizedBox( - height: 35.w, - child: TabBar( - tabAlignment: TabAlignment.start, - indicator: ATFixedWidthTabIndicator( - width: 20.w, - height: 4.w, - gradient: - ref.userProfile?.userSex == 0 - ? LinearGradient( - colors: [ - Color(0xffFFD800), - Color(0xffFF9500), - ], - ) - : LinearGradient( - colors: [ - Color(0xffFFD800), - Color(0xffFF9500), - ], - ), - ), - labelPadding: EdgeInsets.symmetric( - horizontal: 12.w, - ), - labelColor: Colors.black, - isScrollable: true, - unselectedLabelColor: Color( - 0xffB1B1B1, - ), - labelStyle: TextStyle( - fontSize: 15.sp, - fontWeight: FontWeight.bold, - ), - unselectedLabelStyle: TextStyle( - fontSize: 13.sp, - ), - indicatorColor: Colors.transparent, - dividerColor: Colors.transparent, - controller: _tabController, - tabs: _tabs, - ), - ), - SizedBox(width: 6.w), - ], - ), - Expanded( - child: TabBarView( - controller: _tabController, - children: _pages, - ), - ), - SizedBox(height: 75.w), - ], - ), + headerSliverBuilder: (context, innerBoxIsScrolled) { + return [ + SliverToBoxAdapter( + child: Column( + children: [ + SizedBox(height: 172.w), + _buildProfilePanel(ref), + ], ), ), - ), - // 底部按钮区域 - isBlacklistLoading || isBlacklist - ? Container() - : Positioned( - bottom: 0, - left: 0, - right: 0, - child: - widget.isMe == "true" - ? Container() - : (isLoading - ? Container() - : Container( - alignment: AlignmentDirectional.center, - width: ScreenUtil().screenWidth, - height: 75.w, - color: Color(0xffFDF7E5), - child: Column( - children: [ - Container( - height: 0.5.w, - color: Colors.black12, - ), - SizedBox(height: 10.w), - Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - ATDebounceWidget( - debounceTime: Duration( - milliseconds: 800, - ), - child: SizedBox( - width: 110.w, - child: Column( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Image.asset( - "atu_images/person/at_icon_person_in_room.png", - width: 23.w, - ), - SizedBox(height: 5.w), - Container( - margin: EdgeInsets.only( - bottom: 0.w, - ), - child: text( - ATAppLocalizations.of( - context, - )!.inRoom, - textColor: Color( - 0xff5C2C00, - ), - fontWeight: - FontWeight.w600, - fontSize: 14.sp, - ), - ), - ], - ), - ), - onTap: () { - if ((ref - .userProfile - ?.inRoomId ?? - "") - .isNotEmpty) { - ATRoomUtils.goRoom( - ref.userProfile?.inRoomId ?? - "", - context, - ); - } - }, - ), - SizedBox(width: 10.w), - ATDebounceWidget( - debounceTime: Duration( - milliseconds: 800, - ), - child: Container( - width: 110.w, - child: Column( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Image.asset( - "atu_images/person/at_icon_person_tochat.png", - width: 23.w, - ), - SizedBox(height: 5.w), - Container( - margin: EdgeInsets.only( - bottom: 0.w, - ), - child: text( - ATAppLocalizations.of( - context, - )!.message, - textColor: Color( - 0xff5C2C00, - ), - fontWeight: - FontWeight.w600, - fontSize: 14.sp, - ), - ), - ], - ), - ), - onTap: () async { - var conversation = - V2TimConversation( - type: - ConversationType - .V2TIM_C2C, - userID: widget.tageId, - conversationID: '', - ); - var bool = await Provider.of< - RtmProvider - >( - context, - listen: false, - ).startConversation( - conversation, - ); - if (!bool) return; - var json = jsonEncode( - conversation.toJson(), - ); - ATNavigatorUtils.push( - context, - "${ATChatRouter.chat}?conversation=${Uri.encodeComponent(json)}", - ); - }, - ), - SizedBox(width: 10.w), - ATDebounceWidget( - debounceTime: Duration( - milliseconds: 800, - ), - child: Container( - width: 110.w, - child: Column( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Image.asset( - "atu_images/person/at_icon_person_follow.png", - width: 23.w, - ), - SizedBox(height: 5.w), - Container( - margin: EdgeInsets.only( - bottom: 0.w, - ), - child: text( - isFollow - ? ATAppLocalizations.of( - context, - )!.following - : ATAppLocalizations.of( - context, - )!.follow, - textColor: Color( - 0xff5C2C00, - ), - fontWeight: - FontWeight.w600, - fontSize: 14.sp, - ), - ), - ], - ), - ), - onTap: () { - AccountRepository() - .followUser(widget.tageId) - .then((v) { - isFollow = !isFollow; - setState(() {}); - }); - }, - ), - ], - ), - ], - ), - )), + ]; + }, + body: _buildTabContent(ref), ), - ], + _buildTopBar(ref), + if (!hideProfile && widget.isMe != "true") + _buildBottomActions(ref), + ], + ), ), ), ); @@ -1324,6 +225,742 @@ class _PersonDetailPageState extends State ); } + Widget _buildTopBackground(List backgroundPhotos) { + return Positioned.fill( + child: Stack( + children: [ + Column( + children: [ + SizedBox( + height: 285.w, + child: + backgroundPhotos.isNotEmpty + ? CarouselSlider( + options: CarouselOptions( + height: 285.w, + autoPlay: backgroundPhotos.length > 1, + enlargeCenterPage: false, + aspectRatio: 1, + enableInfiniteScroll: true, + autoPlayAnimationDuration: const Duration( + milliseconds: 800, + ), + viewportFraction: 1, + ), + items: + backgroundPhotos.map((item) { + return netImage( + url: item.url ?? "", + width: ScreenUtil().screenWidth, + height: 285.w, + fit: BoxFit.cover, + ); + }).toList(), + ) + : Image.asset( + 'atu_images/person/at_icon_my_head_bg_defalt.png', + width: ScreenUtil().screenWidth, + height: 285.w, + fit: BoxFit.cover, + ), + ), + Expanded( + child: Container( + width: ScreenUtil().screenWidth, + color: const Color(0xffFDF7E5), + ), + ), + ], + ), + Positioned( + top: 172.w - _scrollOffset, + left: 0, + right: 0, + height: ScreenUtil().screenHeight + _scrollOffset + 220.w, + child: _buildFrostedScrollBackground( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8.w), + topRight: Radius.circular(8.w), + ), + ), + ), + ], + ), + ); + } + + Widget _buildFrostedScrollBackground({BorderRadius? borderRadius}) { + return ClipRRect( + borderRadius: borderRadius ?? BorderRadius.zero, + child: Stack( + fit: StackFit.expand, + children: [ + ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 0, sigmaY: 30), + child: Image.asset( + 'atu_images/index/at_person_detail_bag.png', + fit: BoxFit.fitWidth, + alignment: Alignment.topCenter, + repeat: ImageRepeat.repeatY, + ), + ), + BackdropFilter( + filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6), + child: Container(color: Colors.white.withValues(alpha: 0.18)), + ), + ], + ), + ); + } + + Widget _buildTopBar(ChatVibeUserProfileManager ref) { + return Positioned( + left: 0, + right: 0, + top: 0, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: _buildTopBarBackgroundImage(ref), + fit: BoxFit.cover, + alignment: Alignment.topCenter, + ), + ), + child: ChatVibeStandardAppBar( + backButtonColor: Colors.white, + title: "", + titleWidget: _buildCollapsedTitle(ref), + leading: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Navigator.pop(context); + }, + child: Row( + children: [ + Container( + width: width(50), + height: height(30), + alignment: AlignmentDirectional.centerStart, + padding: EdgeInsetsDirectional.only(start: width(15)), + child: Icon( + PlatformDispatcher.instance.locale.languageCode == "ar" + ? Icons.keyboard_arrow_right + : Icons.keyboard_arrow_left, + size: 28.w, + color: Colors.white, + ), + ), + ], + ), + ), + actions: [ + Builder( + builder: (ct) { + return IconButton( + icon: + widget.isMe == "true" + ? Image.asset( + "atu_images/person/at_icon_edit_user_info2.png", + width: 22.w, + color: Colors.white, + ) + : Icon( + Icons.more_horiz, + size: 24.w, + color: Colors.white, + ), + onPressed: () { + if (widget.isMe == "true") { + ATNavigatorUtils.push(context, MainRoute.edit); + return; + } + SmartDialog.showAttach( + tag: "showUserInfoOptMenu", + targetContext: ct, + alignment: Alignment.bottomCenter, + maskColor: Colors.transparent, + animationType: SmartAnimationType.fade, + scalePointBuilder: + (selfSize) => Offset(selfSize.width, 10), + builder: (_) { + return GestureDetector( + child: Transform.translate( + offset: const Offset(0, -10), + child: buildOptUserMenu(context, ref), + ), + onTap: () { + SmartDialog.dismiss(tag: "showUserInfoOptMenu"); + }, + ); + }, + ); + }, + ); + }, + ), + SizedBox(width: 8.w), + ], + ), + ), + ); + } + + ImageProvider _buildTopBarBackgroundImage(ChatVibeUserProfileManager ref) { + final backgroundPhoto = + (ref.userProfile?.backgroundPhotos ?? []) + .where( + (item) => item.status == 1 && (item.url?.isNotEmpty ?? false), + ) + .firstOrNull; + + if (backgroundPhoto?.url?.isNotEmpty ?? false) { + return NetworkImage(backgroundPhoto!.url!); + } + + return const AssetImage('atu_images/person/at_icon_my_head_bg_defalt.png'); + } + + Widget _buildCollapsedTitle(ChatVibeUserProfileManager ref) { + if (_opacity <= 0.55) { + return const SizedBox.shrink(); + } + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + head( + url: ref.userProfile?.userAvatar ?? "", + width: 38.w, + height: 38.w, + headdress: ref.userProfile?.getHeaddress()?.sourceUrl, + ), + SizedBox(width: 8.w), + chatvibeNickNameText( + maxWidth: 145.w, + ref.userProfile?.userNickname ?? "", + fontSize: 15.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + type: ref.userProfile?.getVIP()?.name ?? "", + needScroll: + (ref.userProfile?.userNickname?.characters.length ?? 0) > 13, + ), + ], + ); + } + + Widget _buildProfilePanel(ChatVibeUserProfileManager ref) { + return Container( + width: ScreenUtil().screenWidth, + padding: EdgeInsets.fromLTRB(10.w, 10.w, 10.w, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildUserHeader(ref), + SizedBox(height: 10.w), + _buildHonorRow(ref), + SizedBox(height: 10.w), + _buildCountryAndBadgeRow(ref), + SizedBox(height: 10.w), + _buildStats(ref), + ], + ), + ); + } + + Widget _buildUserHeader(ChatVibeUserProfileManager ref) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildAvatar(ref), + SizedBox(width: 10.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Flexible( + child: chatvibeNickNameText( + maxWidth: 155.w, + ref.userProfile?.userNickname ?? "", + fontSize: 18.sp, + textColor: const Color(0xFFB044FF), + fontWeight: FontWeight.w700, + type: ref.userProfile?.getVIP()?.name ?? "", + needScroll: + (ref.userProfile?.userNickname?.characters.length ?? + 0) > + 13, + ), + ), + SizedBox(width: 6.w), + getVIPBadge( + ref.userProfile?.getVIP()?.name, + width: 65.w, + height: 28.w, + ), + ], + ), + SizedBox(height: 4.w), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + getIdIcon( + ref.userProfile?.wealthLevel ?? 0, + width: 24.w, + height: 24.w, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + textColor: const Color(0xFF333333), + ), + chatvibeNickNameText( + maxWidth: 92.w, + ref.userProfile?.getID() ?? "", + textColor: const Color(0xFF8F2DFF), + fontSize: 15.sp, + fontWeight: FontWeight.w700, + type: ref.userProfile?.getVIP()?.name ?? "", + needScroll: + (ref.userProfile?.getID().characters.length ?? 0) > + 10, + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Clipboard.setData( + ClipboardData(text: ref.userProfile?.getID() ?? ""), + ); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + child: Padding( + padding: EdgeInsets.all(2.w), + child: Image.asset( + "atu_images/index/at_icon_user_card_copy_id.png", + width: 14.w, + height: 14.w, + ), + ), + ), + SizedBox(width: 4.w), + _buildAgeTag(ref), + SizedBox(width: 4.w), + getWealthLevel( + ref.userProfile?.wealthLevel ?? 0, + width: 50.w, + height: 26.w, + ), + SizedBox(width: 4.w), + getUserLevel( + ref.userProfile?.charmLevel ?? 0, + width: 50.w, + height: 26.w, + ), + ], + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildAvatar(ChatVibeUserProfileManager ref) { + if (ref.userProfile?.cpList?.isNotEmpty ?? false) { + return SizedBox( + width: 112.w, + height: 72.w, + child: Stack( + alignment: Alignment.center, + children: [ + PositionedDirectional( + start: 0, + child: GestureDetector( + onTap: () { + final encodedUrls = Uri.encodeComponent( + jsonEncode([ref.userProfile?.cpList?.first.meUserAvatar]), + ); + ATNavigatorUtils.push( + context, + "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0", + ); + }, + child: head( + url: ref.userProfile?.cpList?.first.meUserAvatar ?? "", + width: 72.w, + headdress: ref.userProfile?.getHeaddress()?.sourceUrl, + ), + ), + ), + PositionedDirectional( + end: 0, + child: GestureDetector( + onTap: () { + ATNavigatorUtils.push( + context, + replace: true, + "${MainRoute.person}?isMe=${UserManager().getCurrentUser()?.userProfile?.id == ref.userProfile?.cpList?.first.cpUserId}&tageId=${ref.userProfile?.cpList?.first.cpUserId}", + ); + }, + child: head( + url: ref.userProfile?.cpList?.first.cpUserAvatar ?? "", + headdress: ref.userProfile?.cpList?.first.cpUserAvatarFrame, + width: 72.w, + ), + ), + ), + IgnorePointer( + child: netImage( + url: ref.userProfile?.cpList?.first.selfRing?.sourceUrl ?? "", + height: 58.w, + width: 58.w, + noDefaultImg: false, + ), + ), + ], + ), + ); + } + + return GestureDetector( + child: head( + url: ref.userProfile?.userAvatar ?? "", + width: 72.w, + height: 72.w, + headdress: ref.userProfile?.getHeaddress()?.sourceUrl, + ), + onTap: () { + final encodedUrls = Uri.encodeComponent( + jsonEncode([ref.userProfile?.userAvatar]), + ); + ATNavigatorUtils.push( + context, + "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0", + ); + }, + ); + } + + Widget _buildAgeTag(ChatVibeUserProfileManager ref) { + return Container( + width: (ref.userProfile?.age ?? 0) > 999 ? 52.w : 42.w, + height: 22.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ref.userProfile?.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(ref.userProfile?.userSex), + text( + "${ref.userProfile?.age ?? 0}", + textColor: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 2.w), + ], + ), + ); + } + + Widget _buildHonorRow(ChatVibeUserProfileManager ref) { + final honors = (ref.userProfile?.wearHonor ?? []).where( + (item) => item.use ?? false, + ); + if (honors.isEmpty) { + return const SizedBox.shrink(); + } + return SizedBox( + height: 24.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: honors.length, + itemBuilder: (context, index) { + final item = honors.toList()[index]; + return netImage(url: item.animationUrl ?? "", height: 24.w); + }, + separatorBuilder: (_, __) => SizedBox(width: 4.w), + ), + ); + } + + Widget _buildCountryAndBadgeRow(ChatVibeUserProfileManager ref) { + final badges = + (ref.userProfile?.wearBadge ?? []) + .where((item) => item.use ?? false) + .toList(); + final flagUrl = + Provider.of( + context, + listen: false, + ).getCountryByName(ref.userProfile?.countryName ?? "")?.nationalFlag ?? + ""; + + return SizedBox( + height: 24.w, + child: ListView( + scrollDirection: Axis.horizontal, + children: badges.map( + (item) => Padding( + padding: EdgeInsetsDirectional.only(end: 4.w), + child: netImage( + width: 24.w, + height: 24.w, + url: item.selectUrl ?? "", + ), + ), + ).toList(), + ), + ); + } + + Widget _buildRoundFlag(String flagUrl) { + return Container( + width: 24.w, + height: 24.w, + padding: EdgeInsets.all(5.w), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withValues(alpha: 0.7), + border: Border.all(color: Colors.white, width: 1.w), + ), + child: netImage( + url: flagUrl, + width: 14.w, + height: 14.w, + borderRadius: BorderRadius.circular(7.w), + ), + ); + } + + Widget _buildStats(ChatVibeUserProfileManager ref) { + return Container( + height: 60.w, + alignment: AlignmentDirectional.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildStatItem( + "${counterMap["INTERVIEW"]?.quantity ?? 0}", + ATAppLocalizations.of(context)!.vistors, + () { + if (widget.isMe == "true") { + ATNavigatorUtils.push( + context, + "${MainRoute.vistors}?removePreviousPersonDetail=true", + ); + } + }, + ), + _buildDivider(), + _buildStatItem( + "${counterMap["SUBSCRIPTION"]?.quantity ?? 0}", + ATAppLocalizations.of(context)!.follow, + () { + if (widget.isMe == "true") { + ATNavigatorUtils.push( + context, + "${MainRoute.follow}?removePreviousPersonDetail=true", + ); + } + }, + ), + _buildDivider(), + _buildStatItem( + "${counterMap["FANS"]?.quantity ?? 0}", + ATAppLocalizations.of(context)!.fans, + () { + if (widget.isMe == "true") { + ATNavigatorUtils.push( + context, + "${MainRoute.fans}?removePreviousPersonDetail=true", + ); + } + }, + ), + ], + ), + ); + } + + Widget _buildStatItem(String value, String label, VoidCallback onTap) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: SizedBox( + width: 96.w, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + value, + fontSize: 17.sp, + textColor: const Color(0xff333333), + fontWeight: FontWeight.bold, + ), + SizedBox(height: 2.w), + text(label, fontSize: 13.sp, textColor: const Color(0xffB1B1B1)), + ], + ), + ), + ); + } + + Widget _buildDivider() { + return Container( + width: 1.w, + height: 18.w, + color: const Color(0xff333333).withValues(alpha: 0.35), + ); + } + + Widget _buildTabContent(ChatVibeUserProfileManager ref) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 3.w), + color: Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 35.w, + child: TabBar( + tabAlignment: TabAlignment.start, + indicator: ATFixedWidthTabIndicator( + width: 12.w, + height: 3.w, + gradient: const LinearGradient( + colors: [Color(0xffFFD800), Color(0xffFF9500)], + ), + ), + labelPadding: EdgeInsets.symmetric(horizontal: 12.w), + labelColor: const Color(0xff333333), + isScrollable: true, + unselectedLabelColor: const Color(0xffB1B1B1), + labelStyle: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + unselectedLabelStyle: TextStyle(fontSize: 12.sp), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + ), + Expanded( + child: TabBarView(controller: _tabController, children: _pages), + ), + if (widget.isMe != "true") SizedBox(height: 86.w), + ], + ), + ); + } + + Widget _buildBottomActions(ChatVibeUserProfileManager ref) { + if (isLoading) { + return const SizedBox.shrink(); + } + return Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + height: 86.w, + decoration: BoxDecoration( + color: const Color(0xffFDF7E5), + border: Border(top: BorderSide(color: Colors.black12, width: 0.5.w)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildBottomAction( + icon: "atu_images/person/at_icon_person_in_room.png", + label: ATAppLocalizations.of(context)!.inRoom, + onTap: () { + if ((ref.userProfile?.inRoomId ?? "").isNotEmpty) { + ATRoomUtils.goRoom(ref.userProfile?.inRoomId ?? "", context); + } + }, + ), + _buildBottomAction( + icon: "atu_images/person/at_icon_person_tochat.png", + label: ATAppLocalizations.of(context)!.message, + onTap: () async { + var conversation = V2TimConversation( + type: ConversationType.V2TIM_C2C, + userID: widget.tageId, + conversationID: '', + ); + var ok = await Provider.of( + context, + listen: false, + ).startConversation(conversation); + if (!ok || !mounted) return; + var json = jsonEncode(conversation.toJson()); + ATNavigatorUtils.push( + context, + "${ATChatRouter.chat}?conversation=${Uri.encodeComponent(json)}", + ); + }, + ), + _buildBottomAction( + icon: "atu_images/person/at_icon_person_follow.png", + label: + isFollow + ? ATAppLocalizations.of(context)!.following + : ATAppLocalizations.of(context)!.follow, + onTap: () { + AccountRepository().followUser(widget.tageId).then((v) { + isFollow = !isFollow; + setState(() {}); + }); + }, + ), + ], + ), + ), + ); + } + + Widget _buildBottomAction({ + required String icon, + required String label, + required VoidCallback onTap, + }) { + return ATDebounceWidget( + debounceTime: const Duration(milliseconds: 800), + onTap: onTap, + child: SizedBox( + width: 110.w, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset(icon, width: 25.w, height: 25.w), + SizedBox(height: 6.w), + text( + label, + textColor: const Color(0xff5C2C00), + fontWeight: FontWeight.w700, + fontSize: 12.sp, + ), + ], + ), + ), + ); + } + Container buildOptUserMenu( BuildContext context, ChatVibeUserProfileManager ref, @@ -1589,39 +1226,54 @@ class _PersonDetailPageState extends State alignment: Alignment.center, height: 15.w, child: - (AccountStorage().getCurrentUser()?.userProfile?.userNickname?.length ?? 0) > 8 - ? Marquee( - text: AccountStorage().getCurrentUser()?.userProfile?.userNickname?? "", - style: TextStyle( - fontSize: 10.sp, - color: Color(0xffECB45C), - fontWeight: FontWeight.w600, - decoration: TextDecoration.none, - ), - scrollAxis: Axis.horizontal, - crossAxisAlignment: - CrossAxisAlignment.start, - blankSpace: 20.0, - velocity: 40.0, - pauseAfterRound: Duration(seconds: 1), - accelerationDuration: Duration(seconds: 1), - accelerationCurve: Curves.linear, - decelerationDuration: Duration( - milliseconds: 500, - ), - decelerationCurve: Curves.easeOut, - ) - : Text( - AccountStorage().getCurrentUser()?.userProfile?.userNickname ?? "", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 10.sp, - color: Color(0xffECB45C), - fontWeight: FontWeight.w600, - decoration: TextDecoration.none, - ), - ), + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname + ?.length ?? + 0) > + 8 + ? Marquee( + text: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), ), ), SizedBox(width: 20.w), @@ -1630,39 +1282,39 @@ class _PersonDetailPageState extends State alignment: Alignment.center, height: 15.w, child: - (ref.userProfile?.userNickname?.length ?? 0) > 8 - ? Marquee( - text: ref.userProfile?.userNickname ?? "", - style: TextStyle( - fontSize: 10.sp, - color: Color(0xffECB45C), - fontWeight: FontWeight.w600, - decoration: TextDecoration.none, - ), - scrollAxis: Axis.horizontal, - crossAxisAlignment: - CrossAxisAlignment.start, - blankSpace: 20.0, - velocity: 40.0, - pauseAfterRound: Duration(seconds: 1), - accelerationDuration: Duration(seconds: 1), - accelerationCurve: Curves.linear, - decelerationDuration: Duration( - milliseconds: 500, - ), - decelerationCurve: Curves.easeOut, - ) - : Text( - ref.userProfile?.userNickname ?? "", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 10.sp, - color: Color(0xffECB45C), - fontWeight: FontWeight.w600, - decoration: TextDecoration.none, - ), - ), + (ref.userProfile?.userNickname?.length ?? 0) > 8 + ? Marquee( + text: ref.userProfile?.userNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + ref.userProfile?.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), ), ), SizedBox(width: 15.w), @@ -1768,6 +1420,7 @@ class _PersonDetailPageState extends State AccountRepository() .cpRelationshipDismissApply(ref.userProfile?.id ?? "") .then((result) { + if (!mounted) return; SmartDialog.dismiss(tag: "showPartWaysDialog"); ATTts.show( ATAppLocalizations.of( diff --git a/lib/chatvibe_managers/app_general_manager.dart b/lib/chatvibe_managers/app_general_manager.dart index 820b117..c832adf 100644 --- a/lib/chatvibe_managers/app_general_manager.dart +++ b/lib/chatvibe_managers/app_general_manager.dart @@ -1,10 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svga/flutter_svga.dart'; import 'package:aslan/chatvibe_data/sources/local/file_cache_manager.dart'; import 'package:aslan/chatvibe_data/sources/repositories/config_repository_imp.dart'; import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; -import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; -import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; import 'package:aslan/chatvibe_data/models/country_mode.dart'; import 'package:aslan/chatvibe_domain/models/res/at_banner_leaderboard_res.dart'; import 'package:aslan/chatvibe_domain/models/res/country_res.dart'; @@ -25,6 +22,7 @@ class AppGeneralManager extends ChangeNotifier { Map _giftByIdMap = {}; Map> giftByTab = {}; + List giftTabs = []; Map> activityGiftByTab = {}; ///选中的国家 @@ -115,35 +113,52 @@ class AppGeneralManager extends ChangeNotifier { try { giftResList = await ChatRoomRepository().giftList(); giftByTab.clear(); + giftTabs.clear(); + final apiGiftTabs = []; for (var gift in giftResList) { - giftByTab[gift.giftTab]; - var gmap = giftByTab[gift.giftTab]; + final giftTab = gift.giftTab; + if (giftTab == null || giftTab.isEmpty) { + continue; + } + if (!apiGiftTabs.contains(giftTab)) { + apiGiftTabs.add(giftTab); + } + var gmap = giftByTab[giftTab]; gmap ??= []; gmap.add(gift); - giftByTab[gift.giftTab!] = gmap; + giftByTab[giftTab] = gmap; var gAllMap = giftByTab["ALL"]; gAllMap ??= []; - if (gift.giftTab != "NATIONAL_FLAG" && - gift.giftTab != "ACTIVITY" && - gift.giftTab != "LUCKY_GIFT" && - gift.giftTab != "CP" && - gift.giftTab != "CUSTOMIZED" && - gift.giftTab != "MAGIC") { + if (!_isSpecialGiftTab(giftTab)) { gAllMap.add(gift); } giftByTab["ALL"] = gAllMap; - _giftByIdMap[gift.id!] = gift; + if (gift.id != null) { + _giftByIdMap[gift.id!] = gift; + } } if (includeCustomized) { - giftByTab["CUSTOMIZED"] ??= []; - - ///定制礼物需要一个占位符,显示规则的 - giftByTab["CUSTOMIZED"]?.insert(0, ChatVibeGiftRes(id: "-1000")); + for (final giftTab in apiGiftTabs) { + if (_isCustomizedGiftTab(giftTab) && + (giftByTab[giftTab]?.isNotEmpty ?? false)) { + ///定制礼物需要一个占位符,显示规则的 + giftByTab[giftTab]?.insert(0, ChatVibeGiftRes(id: "-1000")); + } + } } else { giftByTab.remove("CUSTOMIZED"); + giftByTab.remove("EXCLUSIVE"); + apiGiftTabs.removeWhere(_isCustomizedGiftTab); } + if (giftByTab["ALL"]?.isNotEmpty ?? false) { + giftTabs.add("ALL"); + } + giftTabs.addAll( + apiGiftTabs.where((giftTab) => _isSpecialGiftTab(giftTab)), + ); + notifyListeners(); ///先去预下载 @@ -151,6 +166,19 @@ class AppGeneralManager extends ChangeNotifier { } catch (e) {} } + bool _isSpecialGiftTab(String giftTab) { + return giftTab == "NATIONAL_FLAG" || + giftTab == "ACTIVITY" || + giftTab == "LUCKY_GIFT" || + giftTab == "CP" || + _isCustomizedGiftTab(giftTab) || + giftTab == "MAGIC"; + } + + bool _isCustomizedGiftTab(String giftTab) { + return giftTab == "CUSTOMIZED" || giftTab == "EXCLUSIVE"; + } + void giftActivityList() async { try { activityList.clear(); @@ -180,26 +208,9 @@ class AppGeneralManager extends ChangeNotifier { void downLoad(List giftResList) { for (var gift in giftResList) { - if (gift.giftSourceUrl != null) { - if (ATPathUtils.fetchFileExtensionFunction( - gift.giftSourceUrl!, - ).toLowerCase() == - ".svga") { - ///预解析 - if (!ATGiftVapSvgaManager().videoItemCache.containsKey( - gift.giftSourceUrl, - )) { - SVGAParser.shared.decodeFromURL(gift.giftSourceUrl!).then((entity) { - entity.autorelease = false; - ATGiftVapSvgaManager().videoItemCache[gift.giftSourceUrl!] = - entity; - }); - } - } else { - if ((gift.giftSourceUrl ?? "").isNotEmpty) { - FileCacheManager.getInstance().getFile(url: gift.giftSourceUrl!); - } - } + final sourceUrl = gift.giftSourceUrl; + if (sourceUrl != null && sourceUrl.isNotEmpty) { + FileCacheManager.getInstance().getFile(url: sourceUrl); } } } @@ -207,6 +218,7 @@ class AppGeneralManager extends ChangeNotifier { List homeBanners = []; List exploreBanners = []; List roomBanners = []; + List roomListBanners = []; List gameBanners = []; ///首页banner @@ -215,22 +227,25 @@ class AppGeneralManager extends ChangeNotifier { var banners = await ConfigRepositoryImp().getBanner(); homeBanners.clear(); roomBanners.clear(); + roomListBanners.clear(); gameBanners.clear(); exploreBanners.clear(); for (var v in banners) { - if ((v.displayPosition ?? "").contains( - ATBannerType.EXPLORE_PAGE.name, - )) { + List displayPositions = (v.displayPosition ?? "").split(','); + if (displayPositions.contains(ATBannerType.EXPLORE_PAGE.name)) { exploreBanners.add(v); } - if ((v.displayPosition ?? "").contains(ATBannerType.ROOM.name)) { + if (displayPositions.contains(ATBannerType.ROOM.name)) { roomBanners.add(v); } + if (displayPositions.contains(ATBannerType.ROOM_LIST.name)) { + roomListBanners.add(v); + } - if ((v.displayPosition ?? "").contains(ATBannerType.HOME_ALERT.name)) { + if (displayPositions.contains(ATBannerType.HOME_ALERT.name)) { homeBanners.add(v); } - if ((v.displayPosition ?? "").contains(ATBannerType.GAME.name)) { + if (displayPositions.contains(ATBannerType.GAME.name)) { gameBanners.add(v); } } diff --git a/lib/chatvibe_managers/gift_system_manager.dart b/lib/chatvibe_managers/gift_system_manager.dart index 5bbcb2c..827fe78 100644 --- a/lib/chatvibe_managers/gift_system_manager.dart +++ b/lib/chatvibe_managers/gift_system_manager.dart @@ -11,6 +11,12 @@ class ChatVibeGiftSystemManager extends ChangeNotifier { ///幸运礼物就不显示这个特效 bool hideLGiftAnimal = false; + ///从语聊房打开消息页时隐藏幸运礼物麦位动画 + bool hideLGiftAnimalOnRoomMessagePage = false; + + ///从语聊房右下角打开 H5 页时隐藏幸运礼物麦位动画 + bool hideLGiftAnimalOnRoomH5Page = false; + ///挡位是否已经播放了 Map isPlayed = { 10: false, @@ -78,6 +84,22 @@ class ChatVibeGiftSystemManager extends ChangeNotifier { notifyListeners(); } + void updateHideLGiftAnimalOnRoomMessagePage(bool isHide) { + if (hideLGiftAnimalOnRoomMessagePage == isHide) { + return; + } + hideLGiftAnimalOnRoomMessagePage = isHide; + notifyListeners(); + } + + void updateHideLGiftAnimalOnRoomH5Page(bool isHide) { + if (hideLGiftAnimalOnRoomH5Page == isHide) { + return; + } + hideLGiftAnimalOnRoomH5Page = isHide; + notifyListeners(); + } + void reset() { gift = null; luckGiftObtainCoins = 0; diff --git a/lib/chatvibe_managers/localization_manager.dart b/lib/chatvibe_managers/localization_manager.dart index 236932e..ccc82ba 100644 --- a/lib/chatvibe_managers/localization_manager.dart +++ b/lib/chatvibe_managers/localization_manager.dart @@ -6,6 +6,17 @@ import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; class LocalizationManager with ChangeNotifier { + static const List _supportedLanguageCodes = [ + 'en', + 'ar', + 'tr', + 'bn', + 'id', + 'fil', + 'ur', + 'hi', + ]; + Locale? _locale; Locale? get locale => _locale; @@ -17,34 +28,29 @@ class LocalizationManager with ChangeNotifier { init() { String language = DataPersistence.getLang(); if (language.isEmpty) { - Locale systemLocale = ui.window.locale; - if (systemLocale.languageCode.startsWith("ar")) { - _locale = Locale('ar', ''); - } else if (systemLocale.languageCode == "en") { - _locale = Locale('en', ''); - } else if (systemLocale.languageCode == "tr") { - _locale = Locale('tr', ''); - } else if (systemLocale.languageCode == "bn") { - _locale = Locale('bn', ''); - } else { - _locale = Locale('en', ''); - } + Locale systemLocale = ui.PlatformDispatcher.instance.locale; + _locale = Locale(_normalizeLanguageCode(systemLocale.languageCode), ''); } else { - if (language.startsWith("ar")) { - _locale = Locale('ar', ''); - } else if (language == "tr") { - _locale = Locale('tr', ''); - } else if (language == "bn") { - _locale = Locale('bn', ''); - } else { - _locale = Locale('en', ''); - } + _locale = Locale(_normalizeLanguageCode(language), ''); } if (_locale != null) { setLocale(_locale!); } } + String _normalizeLanguageCode(String languageCode) { + if (languageCode.startsWith("ar")) { + return 'ar'; + } + if (languageCode == 'tl') { + return 'fil'; + } + if (_supportedLanguageCodes.contains(languageCode)) { + return languageCode; + } + return 'en'; + } + void setLocale(Locale locale) { _locale = locale; ATGlobalConfig.lang = locale.languageCode; diff --git a/lib/chatvibe_managers/room_manager.dart b/lib/chatvibe_managers/room_manager.dart index 4beb60a..f04cb12 100644 --- a/lib/chatvibe_managers/room_manager.dart +++ b/lib/chatvibe_managers/room_manager.dart @@ -8,6 +8,7 @@ import 'package:aslan/chatvibe_domain/models/res/at_edit_room_info_res.dart'; import 'package:aslan/chatvibe_domain/models/res/my_room_res.dart'; import 'package:aslan/chatvibe_domain/models/res/at_room_contribute_level_res.dart'; + class ChatVibeRoomManager extends ChangeNotifier { MyRoomRes? myRoom; ATRoomContributeLevelRes? roomContributeLevelRes; @@ -26,21 +27,23 @@ class ChatVibeRoomManager extends ChangeNotifier { } } - void createRoom(BuildContext context) async { + Future createRoom(BuildContext context) async { + final createRoomSuccessText = + ATAppLocalizations.of(context)!.createRoomSuccsess; ATLoadingManager.exhibitOperation(context: context); await AccountRepository().createRoom(); myRoom = await AccountRepository().myProfile(); - ATTts.show(ATAppLocalizations.of(context)!.createRoomSuccsess); + ATTts.show(createRoomSuccessText); notifyListeners(); ATLoadingManager.veilRoutine(); + return myRoom; } void contributionLevel(String roomId) { roomContributeLevelRes = null; - ChatRoomRepository().roomContributionActivity(roomId).then((value){ + ChatRoomRepository().roomContributionActivity(roomId).then((value) { roomContributeLevelRes = value; notifyListeners(); }); - } } diff --git a/lib/chatvibe_managers/rtc_manager.dart b/lib/chatvibe_managers/rtc_manager.dart index 286ccde..e287a26 100644 --- a/lib/chatvibe_managers/rtc_manager.dart +++ b/lib/chatvibe_managers/rtc_manager.dart @@ -655,66 +655,66 @@ class RealTimeCommunicationManager extends ChangeNotifier { ); final int roomSessionId = _nextRoomSessionId(); if (currenRoom?.entrants?.roles == ATRoomRolesType.TOURIST.name) { - SmartDialog.show( - tag: "showJoinRoomMember", - alignment: Alignment.center, - animationType: SmartAnimationType.fade, - clickMaskDismiss: false, - backType: SmartBackType.block, - builder: (_) { - return JoinRoomMemberPage( - currenRoom?.roomProfile?.roomProfile?.id ?? "", - () { - ATLoadingManager.exhibitOperation(context: context); - //加入会员 - ChatRoomRepository() - .changeRoomRole( - roomId, - ATRoomRolesType.ADMIN.name, - AccountStorage().getCurrentUser()?.userProfile?.id ?? - "", - AccountStorage().getCurrentUser()?.userProfile?.id ?? - "", - "ONESELF_JOIN", - ) - .then( - (result) async { - currenRoom = currenRoom?.copyWith( - entrants: currenRoom?.entrants?.copyWith( - roles: ATRoomRolesType.MEMBER.name, - ), - ); - await initRoon( - roomSessionId: roomSessionId, - needOpenRedenvelope: needOpenRedenvelope, - redPackId: redPackId, - ); - ATLoadingManager.veilRoutine(); - notifyListeners(); - }, - onError: (e) { - currenRoom = null; - ATLoadingManager.veilRoutine(); - }, - ); - }, - () { - _nextRoomSessionId(); - currenRoom = null; - }, - () async { - await initRoon( - roomSessionId: roomSessionId, - needOpenRedenvelope: needOpenRedenvelope, - redPackId: redPackId, - ); - ATLoadingManager.veilRoutine(); - notifyListeners(); - }, - ); - }, + // SmartDialog.show( + // tag: "showJoinRoomMember", + // alignment: Alignment.center, + // animationType: SmartAnimationType.fade, + // clickMaskDismiss: false, + // backType: SmartBackType.block, + // builder: (_) { + // return JoinRoomMemberPage( + // currenRoom?.roomProfile?.roomProfile?.id ?? "", + // () { + // ATLoadingManager.exhibitOperation(context: context); + // //加入会员 + // ChatRoomRepository() + // .changeRoomRole( + // roomId, + // ATRoomRolesType.ADMIN.name, + // AccountStorage().getCurrentUser()?.userProfile?.id ?? + // "", + // AccountStorage().getCurrentUser()?.userProfile?.id ?? + // "", + // "ONESELF_JOIN", + // ) + // .then( + // (result) async { + // currenRoom = currenRoom?.copyWith( + // entrants: currenRoom?.entrants?.copyWith( + // roles: ATRoomRolesType.MEMBER.name, + // ), + // ); + // await initRoon( + // roomSessionId: roomSessionId, + // needOpenRedenvelope: needOpenRedenvelope, + // redPackId: redPackId, + // ); + // ATLoadingManager.veilRoutine(); + // notifyListeners(); + // }, + // onError: (e) { + // currenRoom = null; + // ATLoadingManager.veilRoutine(); + // }, + // ); + // }, + // () { + // _nextRoomSessionId(); + // currenRoom = null; + // }, + // () async { + await initRoon( + roomSessionId: roomSessionId, + needOpenRedenvelope: needOpenRedenvelope, + redPackId: redPackId, ); ATLoadingManager.veilRoutine(); + notifyListeners(); + // }, + // ); + // }, + // ); + ATLoadingManager.veilRoutine(); } else { await initRoon( roomSessionId: roomSessionId, @@ -1126,6 +1126,17 @@ class RealTimeCommunicationManager extends ChangeNotifier { } } else { ChatVibeUserProfile? roomWheatUser = roomWheatMap[index]?.user; + if (roomWheatUser != null && + roomWheatUser.id != + AccountStorage().getCurrentUser()?.userProfile?.id && + !isFz() && + !isGL()) { + showBottomInBottomDialog( + context!, + ATRoomUserInfoCard(userId: roomWheatUser.id), + ); + return; + } showBottomInBottomDialog( context!, EmptyMaiSelect(index: index, clickUser: roomWheatUser), diff --git a/lib/chatvibe_managers/rtm_manager.dart b/lib/chatvibe_managers/rtm_manager.dart index 46ebc4d..107c521 100644 --- a/lib/chatvibe_managers/rtm_manager.dart +++ b/lib/chatvibe_managers/rtm_manager.dart @@ -2,6 +2,7 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:io'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; +import 'package:dio/dio.dart'; import 'package:extended_image/extended_image.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -14,7 +15,9 @@ import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; import 'package:aslan/chatvibe_data/sources/local/floating_screen_manager.dart'; import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/main.dart'; import 'package:provider/provider.dart'; import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart'; import 'package:tencent_cloud_chat_sdk/enum/V2TimConversationListener.dart'; @@ -558,6 +561,7 @@ class RealTimeMessagingManager extends ChangeNotifier { String msg, V2TimConversation toConversation, ) async { + String? chatBubbleCustomData = _buildChatBubbleCustomData(); // 创建文本消息 V2TimValueCallback createTextMessageRes = await TencentImSDKPlugin.v2TIMManager @@ -580,6 +584,7 @@ class RealTimeMessagingManager extends ChangeNotifier { id: id, // 创建的messageid receiver: toConversation.userID!, // 接收人id needReadReceipt: true, + cloudCustomData: chatBubbleCustomData, groupID: '', // 是否需要已读回执 ); if (sendMessageRes.code == 0) { @@ -593,6 +598,17 @@ class RealTimeMessagingManager extends ChangeNotifier { } } + String? _buildChatBubbleCustomData() { + PropsResources? chatBox = AccountStorage().getChatbox(); + String chatBubbleUrl = (chatBox?.expand ?? "").trim(); + if (chatBubbleUrl.isEmpty) { + return null; + } + return jsonEncode({ + "chatBubble": {"id": chatBox?.id, "expand": chatBubbleUrl}, + }); + } + ///发送单聊自定义消息 Future sendC2CCustomMsg( String msg, @@ -638,6 +654,51 @@ class RealTimeMessagingManager extends ChangeNotifier { } } + ///私聊赠送礼物 + Future sendPrivateGift({ + required String userID, + required ChatVibeGiftRes gift, + required int number, + }) async { + if (userID.isEmpty || (gift.id?.isEmpty ?? true)) { + ATTts.show('Gift parameter error'); + return null; + } + try { + return await ChatRoomRepository().givePrivateGift( + userID, + gift.id!, + number, + ); + } catch (e) { + String error = e.toString(); + if (_isPrivateGiftInsufficientBalance(e)) { + if(e is DioException) { + final data = e.response?.data; + error = data["errorMsg"]?.toString() ?? e.toString(); + } + } + ATTts.show(error); + return null; + } + } + + bool _isPrivateGiftInsufficientBalance(Object error) { + if (error is! DioException) { + return false; + } + final data = error.response?.data; + if (data is! Map) { + return false; + } + final errorCode = data["errorCode"]?.toString(); + final errorCodeName = data["errorCodeName"]?.toString(); + final errorMsg = data["errorMsg"]?.toString(); + return errorCodeName == "INSUFFICIENT_BALANCE" || + errorCode == "5000" || + errorMsg == "INSUFFICIENT_BALANCE"; + } + ///发送单聊图片消息 Future sendImageMsg({ List? selectedList, @@ -693,7 +754,9 @@ class RealTimeMessagingManager extends ChangeNotifier { } } else if (ATPathUtils.receiveFileType(entity.path) == "video_pic") { if (entity.lengthSync() > 50000000) { - ATTts.show(ATAppLocalizations.of(context!)!.theVideoSizeCannotExceed); + ATTts.show( + ATAppLocalizations.of(context!)!.theVideoSizeCannotExceed, + ); return; } // 复制一份视频 @@ -705,10 +768,11 @@ class RealTimeMessagingManager extends ChangeNotifier { await entity.copy(newFile.path); } // 创建缩略图 - String? thumbImagePath = await ATMessageUtils.produceFileThumbnailProcess( - newFile.path, - 128, - ); + String? thumbImagePath = + await ATMessageUtils.produceFileThumbnailProcess( + newFile.path, + 128, + ); V2TimValueCallback createVideoMessageRes = await TencentImSDKPlugin.v2TIMManager .getMessageManager() @@ -920,7 +984,7 @@ class RealTimeMessagingManager extends ChangeNotifier { rocketLevel: fdata["giftWindowLevel"], ); OverlayManager().addMessage(msg); - } else if (type == ATRoomMsgType.roomRedPacket) { + } else if (type == ATRoomMsgType.roomRedPacket) { ///红包触发飘屏 var fData = data["data"]; ATFloatingMessage msg = ATFloatingMessage( @@ -947,7 +1011,8 @@ class RealTimeMessagingManager extends ChangeNotifier { ///邀请进入房间 var fdata = data["data"]; ATFloatingMessage msg = ATFloatingMessage.fromJson(fdata); - if (msg.toUserId == AccountStorage().getCurrentUser()?.userProfile?.id && + if (msg.toUserId == + AccountStorage().getCurrentUser()?.userProfile?.id && msg.roomId != Provider.of( context!, @@ -1111,11 +1176,17 @@ class RealTimeMessagingManager extends ChangeNotifier { } else { res = ATRoomThemeListRes(); } - Provider.of(context!, listen: false).updateRoomBG(res); + Provider.of( + context!, + listen: false, + ).updateRoomBG(res); return; } if (msg.type == ATRoomMsgType.emoticons) { - Provider.of(context!, listen: false).starPlayEmoji(msg); + Provider.of( + context!, + listen: false, + ).starPlayEmoji(msg); return; } if (msg.type == ATRoomMsgType.micChange) { @@ -1128,7 +1199,10 @@ class RealTimeMessagingManager extends ChangeNotifier { timeId: micChangePush.data?.timeId, ); } else if (msg.type == ATRoomMsgType.refreshOnlineUser) { - Provider.of(context!, listen: false).getOnlineUsers(); + Provider.of( + context!, + listen: false, + ).getOnlineUsers(); } else if (msg.type == ATRoomMsgType.gameLuckyGift) { var broadCastRes = ATBroadCastLuckGiftPush.fromJson(data); msg.gift = ChatVibeGiftRes(giftPhoto: broadCastRes.data?.giftCover); @@ -1264,7 +1338,10 @@ class RealTimeMessagingManager extends ChangeNotifier { return; } else if (msg.type == ATRoomMsgType.roomRoleChange) { ///房间身份变动 - Provider.of(context!, listen: false).getMicList(); + Provider.of( + context!, + listen: false, + ).getMicList(); if (msg.toUser?.id == AccountStorage().getCurrentUser()?.userProfile?.id) { Provider.of( @@ -1313,7 +1390,10 @@ class RealTimeMessagingManager extends ChangeNotifier { ).roomMannyGameClose(); return; } else if (msg.type == ATRoomMsgType.roomGameCreate) { - Provider.of(context!, listen: false).getLudoGameInfo(); + Provider.of( + context!, + listen: false, + ).getLudoGameInfo(); return; } addMsg(msg); diff --git a/lib/chatvibe_ui/widgets/msg/message_conversation_list_page.dart b/lib/chatvibe_ui/widgets/msg/message_conversation_list_page.dart index 0249819..2c8c91f 100644 --- a/lib/chatvibe_ui/widgets/msg/message_conversation_list_page.dart +++ b/lib/chatvibe_ui/widgets/msg/message_conversation_list_page.dart @@ -344,12 +344,21 @@ class _ConversationItemState extends State { MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) { V2TimCustomElem customElem = conversation.lastMessage!.customElem!; content = customElem.desc ?? ""; - /*if (customElem.extension == 'sendGift') { - content = "[礼物]"; - } else { - content = "[收到一条消息]"; - }*/ - content = ATAppLocalizations.of(context)!.receivedAMessage; + try { + var data = jsonDecode(customElem.data ?? ""); + if (customElem.desc == "PRIVATE_GIFT" || + (data is Map && + data["type"] == "PRIVATE_GIFT")) { + content = ATAppLocalizations.of(context)!.gift2; + } else { + content = ATAppLocalizations.of(context)!.receivedAMessage; + } + } catch (_) { + content = + customElem.desc == "PRIVATE_GIFT" + ? ATAppLocalizations.of(context)!.gift2 + : ATAppLocalizations.of(context)!.receivedAMessage; + } } } } diff --git a/lib/chatvibe_ui/widgets/room/at_room_user_info_card.dart b/lib/chatvibe_ui/widgets/room/at_room_user_info_card.dart index 11dfa93..ba8a9a8 100644 --- a/lib/chatvibe_ui/widgets/room/at_room_user_info_card.dart +++ b/lib/chatvibe_ui/widgets/room/at_room_user_info_card.dart @@ -2,10 +2,12 @@ import 'dart:convert'; import 'package:aslan/main.dart'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:aslan/app_localizations.dart'; import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; import 'package:aslan/chatvibe_features/index/main_route.dart'; @@ -261,6 +263,36 @@ class _ATRoomUserInfoCardState extends State { 0) > 13, ), + SizedBox(width: 4.w), + GestureDetector( + behavior: + HitTestBehavior.opaque, + onTap: () { + Clipboard.setData( + ClipboardData( + text: + ref + .userCardInfo + ?.userProfile + ?.getID() ?? + "", + ), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.copiedToClipboard, + ); + }, + child: Padding( + padding: EdgeInsets.all(2.w), + child: Image.asset( + "atu_images/index/at_icon_user_card_copy_id.png", + width: 16.w, + height: 16.w, + ), + ), + ), ], ), ], diff --git a/lib/chatvibe_ui/widgets/room/effect/vapp_svga_layer_widget.dart b/lib/chatvibe_ui/widgets/room/effect/vapp_svga_layer_widget.dart index 531df7d..865f929 100644 --- a/lib/chatvibe_ui/widgets/room/effect/vapp_svga_layer_widget.dart +++ b/lib/chatvibe_ui/widgets/room/effect/vapp_svga_layer_widget.dart @@ -29,6 +29,7 @@ class _VapPlusSvgaPlayerState extends State @override void dispose() { ATGiftVapSvgaManager().dispose(); + _svgaController.dispose(); super.dispose(); } diff --git a/lib/chatvibe_ui/widgets/room/rocket/room_rocket_yellow_page.dart b/lib/chatvibe_ui/widgets/room/rocket/room_rocket_yellow_page.dart new file mode 100644 index 0000000..acf3a89 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/rocket/room_rocket_yellow_page.dart @@ -0,0 +1,726 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/models/enum/at_activity_reward_props_type.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_rocket_config_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_rocket_status_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:provider/provider.dart'; + +class RoomRocketYellowPage extends StatefulWidget { + const RoomRocketYellowPage({super.key}); + + @override + State createState() => _RoomRocketYellowPageState(); +} + +class _RoomRocketYellowPageState extends State { + final List _levels = []; + ATRoomRocketConfigRes? currernRocketLevel; + ATRoomRocketStatusRes? roomRocketStatus; + bool _isLoading = true; + int _selectedRewardTab = 0; + + @override + void initState() { + super.initState(); + roomRocketStatus = + Provider.of(context, listen: false).roomRocketStatus; + ChatRoomRepository() + .rocketConfigEnabled() + .then((res) { + if (!mounted) return; + _levels + ..clear() + ..addAll(res); + currernRocketLevel = _findCurrentLevel(res); + setState(() { + _isLoading = false; + }); + }) + .catchError((e) { + if (!mounted) return; + setState(() { + _isLoading = false; + }); + }); + } + + ATRoomRocketConfigRes? _findCurrentLevel(List levels) { + if (levels.isEmpty) return null; + final num currentLevel = roomRocketStatus?.level ?? levels.first.level ?? 1; + final match = levels.where((item) => item.level == currentLevel).toList(); + return match.isNotEmpty ? match.first : levels.first; + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Container( + width: ScreenUtil().screenWidth, + height: 623.w, + constraints: BoxConstraints( + maxHeight: ScreenUtil().screenHeight * 0.86, + ), + child: Stack( + clipBehavior: Clip.none, + children: [ + const Positioned.fill(child: _RocketYellowBackground()), + if (_isLoading) + Center( + child: CupertinoActivityIndicator( + color: Colors.white, + radius: 12.w, + ), + ) + else + _buildBody(), + ], + ), + ), + ); + } + + Widget _buildBody() { + if (_levels.isEmpty || currernRocketLevel == null) { + return Center( + child: text( + ATAppLocalizations.of(context)!.noData, + textColor: Colors.white70, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ); + } + return Stack( + children: [ + PositionedDirectional( + top: 4.w, + start: 112.w, + width: 154.w, + height: 196.w, + child: Image.asset( + "atu_images/room/at_icon_room_rocket_lv${currernRocketLevel?.level ?? 1}.png", + fit: BoxFit.contain, + ), + ), + PositionedDirectional( + top: 164.w, + start: 16.w, + width: 26.w, + height: 26.w, + child: _buildMiniButton( + child: text( + "?", + textColor: Colors.white, + fontSize: 18.sp, + fontWeight: FontWeight.w700, + ), + onTap: _showHelpDialog, + ), + ), + PositionedDirectional( + top: 164.w, + end: 16.w, + width: 54.w, + height: 26.w, + child: _buildMiniButton( + child: text( + ATAppLocalizations.of(context)!.roomRocketRecordAction, + textColor: Colors.white, + fontSize: 12.sp, + maxLines: 1, + ), + onTap: _showRecordDialog, + ), + ), + PositionedDirectional( + top: 217.w, + start: 16.w, + end: 16.w, + height: 82.w, + child: _buildLevelRow(), + ), + PositionedDirectional( + top: 312.w, + start: 16.w, + end: 16.w, + height: 12.w, + child: Consumer( + builder: (context, ref, child) { + return _buildProgressBar(ref.roomRocketStatus); + }, + ), + ), + PositionedDirectional( + top: 332.w, + start: 16.w, + end: 16.w, + height: 14.w, + child: Consumer( + builder: (context, ref, child) { + final progress = _progressValue(ref.roomRocketStatus); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + "${ref.roomRocketStatus?.currentEnergy ?? 0}/${currernRocketLevel?.maxEnergy ?? 0}", + textColor: Color(0xFFFFDC37), + fontSize: 12.sp, + fontWeight: FontWeight.w500, + ), + SizedBox(width: 10.w), + text( + "${(progress * 100).clamp(0, 100).toInt()}%", + textColor: Color(0xFFFFDC37), + fontSize: 12.sp, + fontWeight: FontWeight.w500, + ), + ], + ); + }, + ), + ), + PositionedDirectional( + top: 354.w, + start: 16.w, + end: 16.w, + height: 20.w, + child: _buildRewardTabs(), + ), + PositionedDirectional( + top: 386.w, + start: 16.w, + end: 16.w, + height: 148.w, + child: _buildRewardPanel(), + ), + PositionedDirectional( + bottom: 28.w, + start: 56.w, + end: 56.w, + child: text( + ATAppLocalizations.of(context)!.roomRocketResetCountdown, + textColor: Color(0xFFFFDC37).withValues(alpha: 0.55), + fontSize: 12.sp, + maxLines: 1, + textAlign: TextAlign.center, + ), + ), + ], + ); + } + + Widget _buildMiniButton({ + required Widget child, + required VoidCallback onTap, + }) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: Container( + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(13.w), + ), + child: child, + ), + ); + } + + Widget _buildLevelRow() { + return ListView.builder( + scrollDirection: Axis.horizontal, + padding: EdgeInsets.zero, + itemCount: _levels.length * 2 - 1, + itemBuilder: (context, index) { + if (index.isOdd) { + return SizedBox( + width: 18.w, + child: Icon( + Icons.arrow_forward_rounded, + color: Color(0xFFFFC91D).withValues(alpha: 0.65), + size: 18.w, + ), + ); + } + final int levelIndex = index ~/ 2; + final item = _levels[levelIndex]; + final bool selected = item.level == currernRocketLevel?.level; + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + currernRocketLevel = item; + }); + }, + child: SizedBox( + width: 54.w, + height: 82.w, + child: Stack( + alignment: AlignmentDirectional.topCenter, + children: [ + PositionedDirectional( + top: 2.w, + width: 54.w, + height: 54.w, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: + selected + ? RadialGradient( + colors: [ + Color(0xFFFFFFFF), + Color(0xFFFFE263), + Color(0xFFFF9D00), + ], + ) + : RadialGradient( + colors: [ + Colors.white.withValues(alpha: 0.22), + Colors.white.withValues(alpha: 0.08), + ], + ), + border: Border.all( + color: + selected + ? Color(0xFFFFF1A3) + : Color(0xFFFFDC37).withValues(alpha: 0.45), + width: 1.w, + ), + boxShadow: + selected + ? [ + BoxShadow( + color: Color( + 0xFFFFE263, + ).withValues(alpha: 0.7), + blurRadius: 8.w, + spreadRadius: 1.w, + ), + ] + : null, + ), + ), + ), + PositionedDirectional( + top: 6.w, + width: 46.w, + height: 46.w, + child: Image.asset( + "atu_images/room/at_icon_room_rocket_lv${item.level ?? 1}.png", + fit: BoxFit.contain, + ), + ), + ], + ), + ), + ); + }, + ); + } + + Widget _buildProgressBar(ATRoomRocketStatusRes? status) { + final double progress = _progressValue(status); + return LayoutBuilder( + builder: (context, constraints) { + final bool isRtl = Directionality.of(context) == TextDirection.rtl; + final double thumbWidth = 30.w; + final double thumbStart = + (constraints.maxWidth * progress) - thumbWidth / 2; + final double safeStart = + thumbStart.clamp(0, constraints.maxWidth - thumbWidth).toDouble(); + return Stack( + clipBehavior: Clip.none, + children: [ + Positioned.fill( + child: ClipRRect( + borderRadius: BorderRadius.circular(20.w), + child: Stack( + fit: StackFit.expand, + children: [ + Container(color: Color(0xFFFFE940).withValues(alpha: 0.45)), + FractionallySizedBox( + alignment: AlignmentDirectional.centerStart, + widthFactor: progress, + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Color(0xFFFFF1A3), Color(0xFFFFA000)], + ), + ), + ), + ), + ], + ), + ), + ), + PositionedDirectional( + start: safeStart, + top: -6.w, + width: thumbWidth, + height: 25.w, + child: RotatedBox( + quarterTurns: isRtl ? 3 : 1, + child: Image.asset( + "atu_images/room/at_icon_room_rocket_lv${currernRocketLevel?.level ?? 1}.png", + fit: BoxFit.contain, + ), + ), + ), + ], + ); + }, + ); + } + + double _progressValue(ATRoomRocketStatusRes? status) { + final num current = + status?.currentEnergy ?? roomRocketStatus?.currentEnergy ?? 0; + final num max = currernRocketLevel?.maxEnergy ?? status?.maxEnergy ?? 0; + if (max <= 0) return 0; + return (current / max).clamp(0.0, 1.0).toDouble(); + } + + Widget _buildRewardTabs() { + final l10n = ATAppLocalizations.of(context)!; + final labels = [ + l10n.roomRocketRewardTop1, + l10n.roomRocketRewardSetOff, + l10n.roomRocketRewardInRoom, + ]; + return Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: List.generate(labels.length, (index) { + final bool selected = _selectedRewardTab == index; + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + _selectedRewardTab = index; + }); + }, + child: SizedBox( + width: 100.w, + child: text( + "\\\\\\ ${labels[index]} ///", + textColor: + selected + ? Color(0xFFFFFD78) + : Colors.white.withValues(alpha: 0.5), + fontSize: 12.sp, + maxLines: 1, + textAlign: TextAlign.center, + ), + ), + ); + }), + ); + } + + Widget _buildRewardPanel() { + final rewards = currernRocketLevel?.rewardConfig ?? []; + return Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: AlignmentDirectional.topStart, + end: AlignmentDirectional.bottomEnd, + colors: [ + Color(0xFFFFB21D).withValues(alpha: 0.62), + Color(0xFFFF7A00).withValues(alpha: 0.48), + ], + ), + borderRadius: BorderRadius.circular(16.w), + border: Border.all(color: Color(0xFFFFE16A).withValues(alpha: 0.75)), + boxShadow: [ + BoxShadow( + color: Color(0xFFFFD44A).withValues(alpha: 0.42), + blurRadius: 10.w, + spreadRadius: 0, + ), + ], + ), + child: + rewards.isEmpty + ? Center( + child: text( + ATAppLocalizations.of(context)!.noData, + textColor: Colors.white70, + fontSize: 13.sp, + ), + ) + : Row( + children: [ + _buildRewardItem(rewards.first, 124.w, 108.w), + SizedBox(width: 6.w), + Expanded( + child: GridView.builder( + padding: EdgeInsets.zero, + physics: NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + mainAxisSpacing: 6.w, + crossAxisSpacing: 6.w, + ), + itemCount: rewards.length > 7 ? 6 : rewards.length - 1, + itemBuilder: (context, index) { + return _buildRewardItem(rewards[index + 1], 59.w, 51.w); + }, + ), + ), + ], + ), + ); + } + + Widget _buildRewardItem( + RewardConfig reward, + double boxSize, + double iconSize, + ) { + return Container( + width: boxSize, + height: boxSize, + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + color: Color(0xFFFFF4C6).withValues(alpha: 0.18), + borderRadius: BorderRadius.circular(8.w), + border: Border.all(color: Color(0xFFFFD540).withValues(alpha: 0.35)), + ), + child: _buildRewardIcon(reward, iconSize), + ); + } + + Widget _buildRewardIcon(RewardConfig reward, double size) { + if (reward.type == ATActivityRewardATPropsType.GOLD.name) { + return Image.asset( + "atu_images/general/at_icon_jb.png", + width: size, + height: size, + fit: BoxFit.contain, + ); + } + final cover = reward.cover ?? ""; + if (cover.isEmpty) return Container(); + return netImage( + url: cover, + width: size, + height: size, + fit: BoxFit.contain, + noDefaultImg: true, + ); + } + + void _showHelpDialog() { + SmartDialog.show( + tag: "showRoomRocketHelpDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.symmetric(horizontal: 20.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_rocket_hepl_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 13.w), + Image.asset( + ATGlobalConfig.lang == "ar" + ? "atu_images/room/at_icon_room_rocket_rule_text_ar.png" + : "atu_images/room/at_icon_room_rocket_rule_text_en.png", + width: 58.w, + ), + SizedBox(height: 8.w), + text( + ATAppLocalizations.of(context)!.roomRocketHelpTips, + maxLines: 25, + fontSize: 14.sp, + fontWeight: FontWeight.bold, + ), + SizedBox(height: 20.w), + ], + ), + ); + }, + ); + } + + void _showRecordDialog() { + SmartDialog.show( + tag: "showRoomRocketRecordDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) => const _RoomRocketRecordDialog(), + ); + } +} + +class _RoomRocketRecordDialog extends StatelessWidget { + const _RoomRocketRecordDialog(); + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: SizedBox( + width: 360.w, + height: 576.w, + child: Stack( + children: [ + Positioned.fill( + child: IgnorePointer( + child: Image.asset( + "atu_images/room/at_icon_room_rocket_record_bg.png", + fit: BoxFit.fill, + ), + ), + ), + PositionedDirectional( + top: 12.w, + start: 0, + end: 0, + child: text( + ATAppLocalizations.of(context)!.roomRocketRecordAction, + textColor: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textAlign: TextAlign.center, + ), + ), + PositionedDirectional( + top: 27.w, + end: 24.w, + width: 24.w, + height: 24.w, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + SmartDialog.dismiss(tag: "showRoomRocketRecordDialog"); + }, + child: Image.asset( + "atu_images/room/at_icon_room_game_close.png", + fit: BoxFit.contain, + color: Color(0xFFFFF3A8), + ), + ), + ), + PositionedDirectional( + top: 49.w, + start: 52.w, + child: _buildHeaderLabel( + context, + ATAppLocalizations.of(context)!.roomRocketRecordRoom, + ), + ), + PositionedDirectional( + top: 49.w, + start: 214.w, + child: _buildHeaderLabel( + context, + ATAppLocalizations.of(context)!.roomRocketRecordReward, + ), + ), + PositionedDirectional( + top: 80.w, + start: 30.w, + end: 28.w, + bottom: 44.w, + child: Center( + child: text( + ATAppLocalizations.of(context)!.noData, + textColor: Color(0xFFFFFFFF), + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + PositionedDirectional( + start: 0, + end: 0, + bottom: 24.w, + child: text( + ATAppLocalizations.of(context)!.roomRocketRecordLast35Days, + textColor: Colors.white, + fontSize: 12.sp, + maxLines: 1, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ); + } + + Widget _buildHeaderLabel(BuildContext context, String label) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildMark(), + SizedBox(width: 4.w), + text( + label, + textColor: Colors.white, + fontSize: 14.sp, + maxLines: 1, + textAlign: TextAlign.center, + ), + SizedBox(width: 4.w), + Transform( + alignment: Alignment.center, + transform: Matrix4.diagonal3Values(-1, 1, 1), + child: _buildMark(), + ), + ], + ); + } + + Widget _buildMark() { + return Container( + width: 16.w, + height: 8.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Color(0x00FFFFFF), Color(0xFFFFF1A3)], + ), + ), + ); + } +} + +class _RocketYellowBackground extends StatelessWidget { + const _RocketYellowBackground(); + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.vertical(top: Radius.circular(24.w)), + child: Image.asset( + "atu_images/room/at_icon_room_rocket_yellow_bg.png", + fit: BoxFit.cover, + alignment: Alignment.topCenter, + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/room_banner_view.dart b/lib/chatvibe_ui/widgets/room/room_banner_view.dart index 835b61f..e1e7f14 100644 --- a/lib/chatvibe_ui/widgets/room/room_banner_view.dart +++ b/lib/chatvibe_ui/widgets/room/room_banner_view.dart @@ -6,6 +6,7 @@ import 'package:aslan/chatvibe_core/utilities/at_banner_utils.dart'; import 'package:provider/provider.dart'; import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/gift_system_manager.dart'; class RoomBannerView extends StatefulWidget { @override @@ -55,9 +56,24 @@ class _RoomBannerViewState extends State { ref.roomBanners.map((item) { return GestureDetector( child: netImage(url: item.smallCover ?? ""), - onTap: () { + onTap: () async { print('ads:${item.toJson()}'); - ATBannerUtils.openBanner(item, context); + if (!ATBannerUtils.isWebBanner(item)) { + ATBannerUtils.openBanner(item, context); + return; + } + final giftProvider = Provider.of( + context, + listen: false, + ); + giftProvider.updateHideLGiftAnimalOnRoomH5Page(true); + try { + await ATBannerUtils.openBanner(item, context); + } finally { + giftProvider.updateHideLGiftAnimalOnRoomH5Page( + false, + ); + } }, ); }).toList(), diff --git a/lib/chatvibe_ui/widgets/room/room_play_widget.dart b/lib/chatvibe_ui/widgets/room/room_play_widget.dart index ae62f7f..8d29a9f 100644 --- a/lib/chatvibe_ui/widgets/room/room_play_widget.dart +++ b/lib/chatvibe_ui/widgets/room/room_play_widget.dart @@ -1,10 +1,10 @@ import 'package:aslan/chatvibe_ui/widgets/room/redpack/room_redenvelope_list_page.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:aslan/chatvibe_ui/widgets/room/room_banner_view.dart'; import 'package:aslan/chatvibe_ui/widgets/room/room_task_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/rocket/room_rocket_yellow_page.dart'; import 'package:provider/provider.dart'; import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; @@ -17,8 +17,10 @@ import '../../../chatvibe_managers/audio_manager.dart'; import '../../../chatvibe_managers/rtm_manager.dart'; class RoomPlayWidget extends StatefulWidget { + const RoomPlayWidget({super.key}); + @override - _RoomPlayWidgetState createState() => _RoomPlayWidgetState(); + State createState() => _RoomPlayWidgetState(); } class _RoomPlayWidgetState extends State { @@ -44,46 +46,38 @@ class _RoomPlayWidgetState extends State { Selector( selector: (_, provider) => - provider.currentMusicMode != - null, - shouldRebuild: - (prev, next) => prev != next, + provider.currentMusicMode != null, + shouldRebuild: (prev, next) => prev != next, builder: (context, hasMusicMode, _) { return RepaintBoundary( child: - hasMusicMode - ? GestureDetector( - child: Container( - margin: - EdgeInsets.symmetric( - vertical: 5.w, - ), - child: Image.asset( - "atu_images/room/at_icon_room_music_tag.png", - width: 45.w, - height: 45.w, - ), - ), - onTap: () { - SmartDialog.show( - tag: - "showRoomMusicControl", - alignment: - Alignment - .center, - debounce: true, - animationType: - SmartAnimationType - .fade, - clickMaskDismiss: - true, - builder: (_) { - return ATRoomMusicControlPage(); - }, - ); - }, - ) - : Container(), + hasMusicMode + ? GestureDetector( + child: Container( + margin: EdgeInsets.symmetric( + vertical: 5.w, + ), + child: Image.asset( + "atu_images/room/at_icon_room_music_tag.png", + width: 45.w, + height: 45.w, + ), + ), + onTap: () { + SmartDialog.show( + tag: "showRoomMusicControl", + alignment: Alignment.center, + debounce: true, + animationType: + SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return ATRoomMusicControlPage(); + }, + ); + }, + ) + : Container(), ); }, ), @@ -144,46 +138,62 @@ class _RoomPlayWidgetState extends State { ); }, ), + GestureDetector( + child: Container( + margin: EdgeInsets.symmetric(vertical: 5.w), + child: Image.asset( + "atu_images/room/at_icon_room_rocket_lv1.png", + width: 45.w, + height: 45.w, + fit: BoxFit.contain, + ), + ), + onTap: () { + SmartDialog.show( + tag: "showRoomRocketYellowPage", + alignment: Alignment.bottomCenter, + maskColor: Colors.transparent, + animationType: SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return const RoomRocketYellowPage(); + }, + ); + }, + ), Selector( - selector: - (_, p) => - p.redPacketList.isNotEmpty, - shouldRebuild: - (prev, next) => prev != next, + selector: (_, p) => p.redPacketList.isNotEmpty, + shouldRebuild: (prev, next) => prev != next, builder: (context, hasRedPacket, _) { return RepaintBoundary( child: - hasRedPacket - ? GestureDetector( - child: Container( - margin: - EdgeInsets.symmetric( - vertical: 5.w, - ), - child: Image.asset( - "atu_images/room/at_icon_room_redpack_tag.png", - width: 45.w, - height: 45.w, - ), - ), - onTap: () { - SmartDialog.show( - tag: - "showRoomRedenvelopeList", - alignment: - Alignment - .center, - animationType: - SmartAnimationType - .fade, - builder: (_) { - return RoomRedenvelopeListPage(); - }, - ); - }, - ) - : Container(), + hasRedPacket + ? GestureDetector( + child: Container( + margin: EdgeInsets.symmetric( + vertical: 5.w, + ), + child: Image.asset( + "atu_images/room/at_icon_room_redpack_tag.png", + width: 45.w, + height: 45.w, + ), + ), + onTap: () { + SmartDialog.show( + tag: + "showRoomRedenvelopeList", + alignment: Alignment.center, + animationType: + SmartAnimationType.fade, + builder: (_) { + return RoomRedenvelopeListPage(); + }, + ); + }, + ) + : Container(), ); }, ), diff --git a/lib/config/pickImage.dart b/lib/config/pickImage.dart index 017a788..09d58bc 100644 --- a/lib/config/pickImage.dart +++ b/lib/config/pickImage.dart @@ -23,7 +23,7 @@ class ImagePick { ); if (pickedFile != null) { if (!ATPathUtils.fileTypeIsPicAndMp(pickedFile.path)) { - ATTts.show( "Please select atu_images in .jpg, .jpeg, .png format."); + ATTts.show("Please select atu_images in .jpg, .jpeg, .png format."); return null; } return [File(pickedFile.path)]; @@ -74,7 +74,7 @@ class ImagePick { ); if (pickedFile != null) { if (!ATPathUtils.fileTypeIsPic(pickedFile.path)) { - ATTts.show( "Please select atu_images in .jpg, .jpeg, .png format."); + ATTts.show("Please select atu_images in .jpg, .jpeg, .png format."); return null; } return File(pickedFile.path); @@ -86,6 +86,26 @@ class ImagePick { } } + /// 从相册选择单个视频 + static Future pickVideoFromGallery(BuildContext context) async { + try { + final XFile? pickedFile = await _picker.pickVideo( + source: ImageSource.gallery, + ); + if (pickedFile != null) { + if (ATPathUtils.receiveFileType(pickedFile.path) != "video_pic") { + ATTts.show("Please select a video in supported format."); + return null; + } + return File(pickedFile.path); + } + return null; + } catch (e) { + print("视频选择出错: $e"); + return null; + } + } + /// 使用相机拍摄照片 static Future pickFromCamera(BuildContext context) async { try { diff --git a/lib/main.dart b/lib/main.dart index dab7fa8..3eef259 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -367,6 +367,10 @@ class _MyAppState extends State { const Locale('ar', ''), const Locale('tr', ''), const Locale('bn', ''), + const Locale('id', ''), + const Locale('fil', ''), + const Locale('ur', ''), + const Locale('hi', ''), ], navigatorKey: navigatorKey, debugShowCheckedModeBanner: false, diff --git a/local_packages/extended_text_field-16.0.2/CHANGELOG.md b/local_packages/extended_text_field-16.0.2/CHANGELOG.md new file mode 100644 index 0000000..170aa38 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/CHANGELOG.md @@ -0,0 +1,319 @@ +## 16.0.2 + +* Fix issue that the caret offset is not right after pinyin(composing) is completed on windows desktop(#255) + +## 16.0.1 + +* Fix issue that context menu click didn't work on desktop(#250) + +## 16.0.0 + +* Migrate to Flutter 3.24.0 + +## 15.0.0 + +* Migrate to Flutter 3.22.0 + +## 14.0.0 + +* Migrate to Flutter 3.19.0 +* Fix wrong postion of Magnifier + +## 13.0.1 + +* Update readme about HarmonyOS + +## 13.0.0 + +* Migrate to Flutter 3.16.0 (#229) +* Fix wrong caret position (#224,#226) + +## 12.1.0 + +* Migrate to Flutter 3.13.0 + +## 12.0.1 + +* Fix issue that wrong cursor position on macos. (https://github.com/fluttercandies/extended_text_field/issues/210) + +## 12.0.0 + +* Migrate to Flutter 3.10.0 +* Refactoring codes and sync codes from 3.10.0 +* Breaking change: + Remove [ExtendedText.textSelectionGestureDetectorBuilder],[ExtendedText.shouldShowSelectionHandles] +* Add ExtendedSelectableText + +## 11.0.1 + +* fix issue on ios after flutter version 3.7.0. #191 #198 + +## 11.0.0 + +* Migrate to 3.7.0 + +## 10.2.0 + +* Add TextInputBindingMixin to prevent system keyboard show. +* Add No SystemKeyboard demo + +## 10.1.1 + +* Fix issue selection not right #172 + +## 10.1.0 + +* Migrate to 3.0.0 +* Support Scribble Handwriting for iPads + +## 10.0.1 + +* Public ExtendedTextFieldState and add bringIntoView method to support jump to caret when insert text with TextEditingController + +## 10.0.0 + +* Migrate to 2.10.0. +* Add shouldShowSelectionHandles and textSelectionGestureDetectorBuilder call back to define the behavior of handles and toolbar. +* Shortcut support for web and desktop. + +## 9.0.3 + +* Fix hittest is not right #131 + +## 9.0.2 + +* Fix selectionWidthStyle and selectionHeightStyle are not working. + +## 9.0.1 + +* Support to use keyboard move cursor for SpecialInlineSpan. #135 +* Fix issue that backspace delete two chars. #141 + +## 9.0.0 + +* Migrate to 2.8 + +## 8.0.0 + +* Add [SpecialTextSpan.mouseCursor], [SpecialTextSpan.onEnter] and [SpecialTextSpan.onExit]. +* merge code from 2.2.0 + +## 7.0.1 + +* Fix issue that composing is not updated.#122 + +## 7.0.0 + +* Breaking change: [SpecialText.getContent] is not include endflag now.(please check if you call getContent and your endflag length is more than 1) +* Fix demo manualDelete error #120 + +## 6.0.1 + +* Fix issue that toolbar is not shown when double tap +* Fix throw exception when selectWordAtOffset + +## 6.0.0 + +* Support null-safety + +## 5.0.4 + +* Fix toolbar is not show after some behaviour #107 + +## 5.0.3 + +* Fix miss TextStyle for specialTextSpanBuilder #89 + +## 5.0.2 + +* Fix wrong position of caret + +## 5.0.1 + +* change handleSpecialText to hasSpecialInlineSpanBase(extended_text_library) +* add hasPlaceholderSpan(extended_text_library) + +## 5.0.0 + +* Merge from Flutter v1.22 +* Support cursorHeight, onAppPrivateCommand, restorationId + +## 4.0.0 + +* Merge from Flutter v1.20 +* Support Autofill + +## 3.0.1 + +* Fix issue that text is clipped when maxLine is 1 and width is more than maxWidth.(#67,#76) +* Fix issue that handles are not shown when the height of TextStyle is big than 1.0.(#49) + +## 3.0.0 + +* Breaking change: fix typos OverflowWidget. + +## 2.0.0 + +* Breaking change: extended_text_library has changed to support OverFlowWidget ExtendedText +* Fix textSelectionControls is not working. + +## 1.0.1 + +* Fix wrong calculation about selection handles. + +## 1.0.0 + +* Merge code from 1.17.0 +* Fix analysis_options + +## 0.5.0 + +* Fix error caret offset on ios. + +## 0.4.9 + +* Fix error about TargetPlatform.macOS + +## 0.4.8 + +* Fix issue that the cursor returns to the top when deleting quickly in Multi-line text +* Fix issue that toolbar will not show if autofocus is true when longpress + +## 0.4.7 + +* Codes base on 1.12.13+hotfix.5 +* Set limitation of flutter sdk >=1.12.13 <1.12.16 +* Add demo that how to delete text with code +* Fix issue that not showing text while entering text from keyboard + +## 0.4.6 + +* Remove TargetPlatform.macOS + +## 0.4.5 + +* Fix build error for flutter sdk 1.12 + +## 0.4.4 + +* Fix wrong caret hegiht and postion +* Make Ios/Android caret the same height + +## 0.4.3 + +* Fix kMinInteractiveSize is missing in high version of flutter + +## 0.4.2 + +* Support custom selection toolbar and handles +* Improve codes about selection overlay +* Select all SpecialTextSpan(which deleteAll is true) when double tap or long tap +* Support WidgetSpan hitTest + +## 0.4.1 + +* Fix issue that type 'ImageSpan' is not a subtype of type 'textSpan'(https://github.com/fluttercandies/extended_text_field/issues/13) + +## 0.4.0 + +* Fix issue tgat WidgetSpan wrong offset(https://github.com/fluttercandies/extended_text_field/issues/11) +* Fix wrong caret offset + +## 0.3.9 + +* Improve codes base on v1.7.8 +* Support WidgetSpan (ExtendedWidgetSpan) + +## 0.3.7 + +* Update extended_text_library + +## 0.3.4 + +* Remove un-used codes in extended_text_selection + +## 0.3.3 + +* Update extended_text_library + +## 0.3.2 + +* Update path_provider 1.1.0 + +## 0.3.0 + +* Uncomment getFullHeightForCaret method for 1.5.4-hotfix.2 +* Corret selection handles visibility for _updateSelectionExtentsVisibility method + +## 0.2.8 + +* Corret selection handles position for image textspan +* StrutStyle strutStyle is obsoleted, it will lead to bugs for image span size. + +## 0.2.7 + +* Fix selection handles blinking + +## 0.2.6 + +* Take care when TextSpan children is null + +## 0.2.5 + +* Update extended_text_library +1.Remove caretIn parameter(SpecialTextSpan) +2.DeleteAll parameter has the same effect as caretIn parameter(SpecialTextSpan) + +## 0.2.4 + +* Fix caret position about image span +* Add caretIn parameter(whether caret can move into special text for SpecialTextSpan(like a image span or @xxxx)) for SpecialTextSpan + +## 0.2.3 + +* Disabled informationCollector to keep backwards compatibility for now (ExtendedNetworkImageProvider) + +## 0.2.2 + +* Fix caret position for last one image span +* Add image text demo +* Fix position for specialTex + +## 0.2.1 + +* Fix caret position for image span + +## 0.2.0 + +* Only iterate textSpan.children to find SpecialTextSpan + +## 0.1.9 + +* Add BackgroundTextSpan, support to paint custom background + +## 0.1.8 + +* Handle TextEditingValue's composing + +## 0.1.6 + +* Improve codes to avoid unnecessary computation + +## 0.1.5 + +* Override compareTo method in SpecialTextSpan and ImageSpan to + Fix issue that image span or special text span was error rendering + +## 0.1.4 + +* Update limitation +* Improve codes + +## 0.1.3 + +* Update limitation +* Improve codes + +## 0.1.1 + +* Support special text amd inline image diff --git a/local_packages/extended_text_field-16.0.2/CODEOWNERS b/local_packages/extended_text_field-16.0.2/CODEOWNERS new file mode 100644 index 0000000..b0e8c47 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/CODEOWNERS @@ -0,0 +1 @@ +* @zmtzawqlp diff --git a/local_packages/extended_text_field-16.0.2/LICENSE b/local_packages/extended_text_field-16.0.2/LICENSE new file mode 100644 index 0000000..5e61798 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 zmtzawqlp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/local_packages/extended_text_field-16.0.2/README-ZH.md b/local_packages/extended_text_field-16.0.2/README-ZH.md new file mode 100644 index 0000000..91c0b73 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/README-ZH.md @@ -0,0 +1,616 @@ +# extended_text_field + +[![pub package](https://img.shields.io/pub/v/extended_text_field.svg)](https://pub.dartlang.org/packages/extended_text_field) [![GitHub stars](https://img.shields.io/github/stars/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/stargazers) [![GitHub forks](https://img.shields.io/github/forks/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/network) [![GitHub license](https://img.shields.io/github/license/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/blob/master/LICENSE) [![GitHub issues](https://img.shields.io/github/issues/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/issues) flutter-candies + +文档语言: [English](README.md) | 中文简体 + +官方输入框的扩展组件,支持图片,@某人,自定义文字背景。也支持自定义菜单和选择器。 + +[ExtendedTextField 在线 Demo](https://fluttercandies.github.io/extended_text_field/) + +ExtendedTextField 是 Flutter 官方 TextField 的三方扩展库,主要扩展功能如下: + +| 功能 | ExtendedTextField | TextField | +|-----------------------------------------|---------------------------------------------------------|----------------------------------------------------------| +| 图文混合 | 支持,可以实现图文混合显示 | 仅支持显示文本,但在选择文本时会出现问题 | +| 支持复制真实值 | 支持,可以复制出文本的真实值 | 不支持 | +| 根据文本格式快速构建富文本 | 支持,可以根据文本格式快速构建富文本 | 不支持 | + +> 已支持 `HarmonyOS`. 请使用最新的带有 `ohos` 标志的版本. 你可以在 `Versions` 签查找. + +```yaml +dependencies: + extended_text_field: 11.0.1-ohos +``` + + +- [extended\_text\_field](#extended_text_field) + - [限制](#限制) + - [特殊文本](#特殊文本) + - [创建特殊文本](#创建特殊文本) + - [特殊文本Builder](#特殊文本builder) + - [图片](#图片) + - [ImageSpan](#imagespan) + - [缓存图片](#缓存图片) + - [文本选择控制器](#文本选择控制器) + - [WidgetSpan](#widgetspan) + - [阻止系统键盘](#阻止系统键盘) + - [TextInputBindingMixin](#textinputbindingmixin) + - [TextInputFocusNode](#textinputfocusnode) + - [CustomKeyboard](#customkeyboard) + - [☕️Buy me a coffee](#️buy-me-a-coffee) + +## 限制 + +- 不支持TextDirection.rtl,从右向左. + +- 不支持obscureText为true. + +## 特殊文本 + +![](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_text_field/extended_text_field.gif) + +### 创建特殊文本 + +extended_text 帮助将字符串文本快速转换为特殊的TextSpan + +下面的例子告诉你怎么创建一个@xxx + +具体思路是对字符串进行进栈遍历,通过判断flag来判定是否是一个特殊字符。 +例子:@zmtzawqlp ,以@开头并且以空格结束,我们就认为它是一个@的特殊文本 + +```dart +class AtText extends SpecialText { + static const String flag = "@"; + final int start; + + /// whether show background for @somebody + final bool showAtBackground; + + AtText(TextStyle textStyle, SpecialTextGestureTapCallback onTap, + {this.showAtBackground: false, this.start}) + : super( + flag, + " ", + textStyle, + ); + + @override + InlineSpan finishText() { + TextStyle textStyle = + this.textStyle?.copyWith(color: Colors.blue, fontSize: 16.0); + + final String atText = toString(); + + return showAtBackground + ? BackgroundTextSpan( + background: Paint()..color = Colors.blue.withOpacity(0.15), + text: atText, + actualText: atText, + start: start, + + ///caret can move into special text + deleteAll: true, + style: textStyle, + recognizer: (TapGestureRecognizer() + ..onTap = () { + if (onTap != null) onTap(atText); + })) + : SpecialTextSpan( + text: atText, + actualText: atText, + start: start, + style: textStyle, + recognizer: (TapGestureRecognizer() + ..onTap = () { + if (onTap != null) onTap(atText); + })); + } +} + +``` + +### 特殊文本Builder + +创建属于你自己规则的Builder,上面说了你可以继承SpecialText来定义各种各样的特殊文本。 +- build 方法中,是通过具体思路是对字符串进行进栈遍历,通过判断flag来判定是否是一个特殊文本。 + 感兴趣的,可以看一下SpecialTextSpanBuilder里面build方法的实现,当然你也可以写出属于自己的build逻辑 +- createSpecialText 通过判断flag来判定是否是一个特殊文本 + +```dart +class MySpecialTextSpanBuilder extends SpecialTextSpanBuilder { + /// whether show background for @somebody + final bool showAtBackground; + final BuilderType type; + MySpecialTextSpanBuilder( + {this.showAtBackground: false, this.type: BuilderType.extendedText}); + + @override + TextSpan build(String data, {TextStyle textStyle, onTap}) { + var textSpan = super.build(data, textStyle: textStyle, onTap: onTap); + return textSpan; + } + + @override + SpecialText createSpecialText(String flag, + {TextStyle textStyle, SpecialTextGestureTapCallback onTap, int index}) { + if (flag == null || flag == "") return null; + + ///index is end index of start flag, so text start index should be index-(flag.length-1) + if (isStart(flag, AtText.flag)) { + return AtText(textStyle, onTap, + start: index - (AtText.flag.length - 1), + showAtBackground: showAtBackground, + type: type); + } else if (isStart(flag, EmojiText.flag)) { + return EmojiText(textStyle, start: index - (EmojiText.flag.length - 1)); + } else if (isStart(flag, DollarText.flag)) { + return DollarText(textStyle, onTap, + start: index - (DollarText.flag.length - 1), type: type); + } + return null; + } +} +``` +其实你也不是一定要用这套代码将字符串转换为TextSpan,你可以有自己的方法,给最后的TextSpan就可以了。 + +## 图片 + +![](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_text_field/extended_text_field_image.gif) + +### ImageSpan + +使用ImageSpan 展示图片 + +```dart +ImageSpan( + ImageProvider image, { + Key key, + @required double imageWidth, + @required double imageHeight, + EdgeInsets margin, + int start: 0, + ui.PlaceholderAlignment alignment = ui.PlaceholderAlignment.bottom, + String actualText, + TextBaseline baseline, + TextStyle style, + BoxFit fit: BoxFit.scaleDown, + ImageLoadingBuilder loadingBuilder, + ImageFrameBuilder frameBuilder, + String semanticLabel, + bool excludeFromSemantics = false, + Color color, + BlendMode colorBlendMode, + AlignmentGeometry imageAlignment = Alignment.center, + ImageRepeat repeat = ImageRepeat.noRepeat, + Rect centerSlice, + bool matchTextDirection = false, + bool gaplessPlayback = false, + FilterQuality filterQuality = FilterQuality.low, + }) + +ImageSpan(AssetImage("xxx.jpg"), + imageWidth: size, + imageHeight: size, + margin: EdgeInsets.only(left: 2.0, bottom: 0.0, right: 2.0)); + } +``` + +| 参数 | 描述 | 默认 | +| ----------- | ----------------------------------------------------------------- | ---------------- | +| image | 图片展示的Provider(ImageProvider) | - | +| imageWidth | 宽度,不包括 margin | 必填 | +| imageHeight | 高度,不包括 margin | 必填 | +| margin | 图片的margin | - | +| actualText | 真实的文本,当你开启文本选择功能的时候,必须设置,比如图片"\[love\] | 空占位符'\uFFFC' | +| start | 在文本字符串中的开始位置,当你开启文本选择功能的时候,必须设置 | 0 | + +### 缓存图片 + +你可以用ExtendedNetworkImageProvider来缓存文本中的图片,使用clearDiskCachedImages方法来清掉本地缓存 + +引入 extended_image_library + +```dart +dependencies: + extended_image_library: ^0.1.4 +``` + +```dart +ExtendedNetworkImageProvider( + this.url, { + this.scale = 1.0, + this.headers, + this.cache: false, + this.retries = 3, + this.timeLimit, + this.timeRetry = const Duration(milliseconds: 100), + CancellationToken cancelToken, +}) : assert(url != null), + assert(scale != null), + cancelToken = cancelToken ?? CancellationToken(); +``` + +| 参数 | 描述 | 默认 | +| ----------- | ------------------- | ------------------- | +| url | 网络请求地址 | required | +| scale | ImageInfo中的scale | 1.0 | +| headers | HttpClient的headers | - | +| cache | 是否缓存到本地 | false | +| retries | 请求尝试次数 | 3 | +| timeLimit | 请求超时 | - | +| timeRetry | 请求重试间隔 | milliseconds: 100 | +| cancelToken | 用于取消请求的Token | CancellationToken() | + +```dart +/// Clear the disk cache directory then return if it succeed. +/// timespan to compute whether file has expired or not +Future clearDiskCachedImages({Duration duration}) async +``` + +## 文本选择控制器 + +![](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_text_field/custom_toolbar.gif) + +提供了默认的控制器MaterialExtendedTextSelectionControls/CupertinoExtendedTextSelectionControls + +通过重写 [ExtendedTextField.extendedContextMenuBuilder] 和 [TextSelectionControls] 来自定义菜单和选择器。 + +```dart +const double _kHandleSize = 22.0; + +/// Android Material styled text selection controls. +class MyTextSelectionControls extends TextSelectionControls + with TextSelectionHandleControls { + static Widget defaultContextMenuBuilder( + BuildContext context, ExtendedEditableTextState editableTextState) { + return AdaptiveTextSelectionToolbar.buttonItems( + buttonItems: [ + ...editableTextState.contextMenuButtonItems, + ContextMenuButtonItem( + onPressed: () { + launchUrl( + Uri.parse( + 'mailto:zmtzawqlp@live.com?subject=extended_text_share&body=${editableTextState.textEditingValue.text}', + ), + ); + editableTextState.hideToolbar(true); + editableTextState.textEditingValue + .copyWith(selection: const TextSelection.collapsed(offset: 0)); + }, + type: ContextMenuButtonType.custom, + label: 'like', + ), + ], + anchors: editableTextState.contextMenuAnchors, + ); + // return AdaptiveTextSelectionToolbar.editableText( + // editableTextState: editableTextState, + // ); + } + + /// Returns the size of the Material handle. + @override + Size getHandleSize(double textLineHeight) => + const Size(_kHandleSize, _kHandleSize); + + /// Builder for material-style text selection handles. + @override + Widget buildHandle( + BuildContext context, TextSelectionHandleType type, double textLineHeight, + [VoidCallback? onTap, double? startGlyphHeight, double? endGlyphHeight]) { + final Widget handle = SizedBox( + width: _kHandleSize, + height: _kHandleSize, + child: Image.asset( + 'assets/40.png', + ), + ); + + // [handle] is a circle, with a rectangle in the top left quadrant of that + // circle (an onion pointing to 10:30). We rotate [handle] to point + // straight up or up-right depending on the handle type. + switch (type) { + case TextSelectionHandleType.left: // points up-right + return Transform.rotate( + angle: math.pi / 4.0, + child: handle, + ); + case TextSelectionHandleType.right: // points up-left + return Transform.rotate( + angle: -math.pi / 4.0, + child: handle, + ); + case TextSelectionHandleType.collapsed: // points up + return handle; + } + } + + /// Gets anchor for material-style text selection handles. + /// + /// See [TextSelectionControls.getHandleAnchor]. + @override + Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight, + [double? startGlyphHeight, double? endGlyphHeight]) { + switch (type) { + case TextSelectionHandleType.left: + return const Offset(_kHandleSize, 0); + case TextSelectionHandleType.right: + return Offset.zero; + default: + return const Offset(_kHandleSize / 2, -4); + } + } + + @override + bool canSelectAll(TextSelectionDelegate delegate) { + // Android allows SelectAll when selection is not collapsed, unless + // everything has already been selected. + final TextEditingValue value = delegate.textEditingValue; + return delegate.selectAllEnabled && + value.text.isNotEmpty && + !(value.selection.start == 0 && + value.selection.end == value.text.length); + } +} + +``` + +## WidgetSpan + +![](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_text_field/widget_span.gif) + +ExtendedWidgetSpan 支持选择以及hitTest, 所以你可以在输入框中加入任何的widget。 + +```dart +class EmailText extends SpecialText { + final TextEditingController controller; + final int start; + final BuildContext context; + EmailText(TextStyle textStyle, SpecialTextGestureTapCallback onTap, + {this.start, this.controller, this.context, String startFlag}) + : super(startFlag, " ", textStyle, onTap: onTap); + + @override + bool isEnd(String value) { + var index = value.indexOf("@"); + var index1 = value.indexOf("."); + + return index >= 0 && + index1 >= 0 && + index1 > index + 1 && + super.isEnd(value); + } + + @override + InlineSpan finishText() { + final String text = toString(); + + return ExtendedWidgetSpan( + actualText: text, + start: start, + alignment: ui.PlaceholderAlignment.middle, + child: GestureDetector( + child: Padding( + padding: EdgeInsets.only(right: 5.0, top: 2.0, bottom: 2.0), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(5.0)), + child: Container( + padding: EdgeInsets.all(5.0), + color: Colors.orange, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + text.trim(), + //style: textStyle?.copyWith(color: Colors.orange), + ), + SizedBox( + width: 5.0, + ), + InkWell( + child: Icon( + Icons.close, + size: 15.0, + ), + onTap: () { + controller.value = controller.value.copyWith( + text: controller.text + .replaceRange(start, start + text.length, ""), + selection: TextSelection.fromPosition( + TextPosition(offset: start))); + }, + ) + ], + ), + )), + ), + onTap: () { + showDialog( + context: context, + barrierDismissible: true, + builder: (c) { + TextEditingController textEditingController = + TextEditingController()..text = text.trim(); + return Column( + children: [ + Expanded( + child: Container(), + ), + Material( + child: Padding( + padding: EdgeInsets.all(10.0), + child: TextField( + controller: textEditingController, + decoration: InputDecoration( + suffixIcon: FlatButton( + child: Text("OK"), + onPressed: () { + controller.value = controller.value.copyWith( + text: controller.text.replaceRange( + start, + start + text.length, + textEditingController.text + " "), + selection: TextSelection.fromPosition( + TextPosition( + offset: start + + (textEditingController.text + " ") + .length))); + + Navigator.pop(context); + }, + )), + ), + )), + Expanded( + child: Container(), + ) + ], + ); + }); + }, + ), + deleteAll: true, + ); + } +} +``` + +## 阻止系统键盘 + +我们不需要代码侵入到 [ExtendedTextField] 或者 [TextField] 当中, 就可以阻止系统键盘弹出, + +### TextInputBindingMixin + +我们通过阻止 Flutter Framework 发送 `TextInput.show` 到 Flutter 引擎来阻止系统键盘弹出 + +你可以直接使用 [TextInputBinding]. + +``` dart +void main() { + TextInputBinding(); + runApp(const MyApp()); +} +``` + +或者你如果有其他的 `binding`,你可以这样。 + +``` dart + class YourBinding extends WidgetsFlutterBinding with TextInputBindingMixin,YourBindingMixin { + } + + void main() { + YourBinding(); + runApp(const MyApp()); + } +``` + +或者你需要重载 `ignoreTextInputShow` 方法,你可以这样。 + +``` dart + class YourBinding extends TextInputBinding { + @override + // ignore: unnecessary_overrides + bool ignoreTextInputShow() { + // you can override it base on your case + // if NoKeyboardFocusNode is not enough + return super.ignoreTextInputShow(); + } + } + + void main() { + YourBinding(); + runApp(const MyApp()); + } +``` + +### TextInputFocusNode + +把 [TextInputFocusNode] 传递给 [ExtendedTextField] 或者 [TextField]。 + + +``` dart +final TextInputFocusNode _focusNode = TextInputFocusNode(); + + @override + Widget build(BuildContext context) { + return ExtendedTextField( + // request keyboard if need + focusNode: _focusNode..debugLabel = 'ExtendedTextField', + ); + } + + @override + Widget build(BuildContext context) { + return TextField( + // request keyboard if need + focusNode: _focusNode..debugLabel = 'CustomTextField', + ); + } +``` + +我们通过当前的 `FocusNode` 是否是 [TextInputFocusNode],来决定是否阻止系统键盘弹出的。 + +``` dart + final FocusNode? focus = FocusManager.instance.primaryFocus; + if (focus != null && + focus is TextInputFocusNode && + focus.ignoreSystemKeyboardShow) { + return true; + } +``` +### CustomKeyboard + +你可以通过当前焦点的变化的时候,来显示或者隐藏自定义的键盘。 + +当你的自定义键盘可以关闭而不让焦点失去,你应该在 [ExtendedTextField] 或者 [TextField] +的 `onTap` 事件中,再次判断键盘是否显示。 + +``` dart + @override + void initState() { + super.initState(); + _focusNode.addListener(_handleFocusChanged); + } + + void _onTextFiledTap() { + if (_bottomSheetController == null) { + _handleFocusChanged(); + } + } + + void _handleFocusChanged() { + if (_focusNode.hasFocus) { + // just demo, you can define your custom keyboard as you want + _bottomSheetController = showBottomSheet( + context: FocusManager.instance.primaryFocus!.context!, + // set false, if don't want to drag to close custom keyboard + enableDrag: true, + builder: (BuildContext b) { + // your custom keyboard + return Container(); + }); + // maybe drag close + _bottomSheetController?.closed.whenComplete(() { + _bottomSheetController = null; + }); + } else { + _bottomSheetController?.close(); + _bottomSheetController = null; + } + } + + @override + void dispose() { + _focusNode.removeListener(_handleFocusChanged); + super.dispose(); + } +``` + + +查看 [完整的例子](https://github.com/fluttercandies/extended_text_field/tree/master/example/lib/pages/simple/no_keyboard.dart) + +## ☕️Buy me a coffee + +![img](http://zmtzawqlp.gitee.io/my_images/images/qrcode.png) diff --git a/local_packages/extended_text_field-16.0.2/README.md b/local_packages/extended_text_field-16.0.2/README.md new file mode 100644 index 0000000..8c67e2f --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/README.md @@ -0,0 +1,605 @@ +# extended_text_field + +[![pub package](https://img.shields.io/pub/v/extended_text_field.svg)](https://pub.dartlang.org/packages/extended_text_field) [![GitHub stars](https://img.shields.io/github/stars/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/stargazers) [![GitHub forks](https://img.shields.io/github/forks/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/network) [![GitHub license](https://img.shields.io/github/license/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/blob/master/LICENSE) [![GitHub issues](https://img.shields.io/github/issues/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/issues) flutter-candies + +Language: English | [中文简体](README-ZH.md) + +Extended official text field to build special text like inline image, @somebody, custom background etc quickly.It also support to build custom seleciton toolbar and handles. + +[Web demo for ExtendedTextField](https://fluttercandies.github.io/extended_text_field/) + +ExtendedTextField is a third-party extension library for Flutter's official TextField component. The main extended features are as follows: + +| Feature | ExtendedTextField | TextField | +|---------------------------------------|------------------------------------------------------|----------------------------------------------------| +| Inline images and text mixture | Supported, allows displaying inline images and mixed text | Only supports displaying text, but have issues with text selection | +| Copying the actual value | Supported, enables copying the actual value of the text | Not supported | +| Quick construction of rich text | Supported, enables quick construction of rich text based on text format | Not supported | + +> `HarmonyOS` is supported. Please use the latest version which contains `ohos` tag. You can check it in `Versions` tab. + +```yaml +dependencies: + extended_text_field: 11.0.1-ohos +``` + + +Please note that the translation provided above is based on the information you provided in the original text. + +- [extended\_text\_field](#extended_text_field) + - [Limitation](#limitation) + - [Special Text](#special-text) + - [Create Special Text](#create-special-text) + - [SpecialTextSpanBuilder](#specialtextspanbuilder) + - [Image](#image) + - [ImageSpan](#imagespan) + - [Cache Image](#cache-image) + - [TextSelectionControls](#textselectioncontrols) + - [WidgetSpan](#widgetspan) + - [NoSystemKeyboard](#nosystemkeyboard) + - [TextInputBindingMixin](#textinputbindingmixin) + - [TextInputFocusNode](#textinputfocusnode) + +## Limitation + +- Not support: it won't handle special text when TextDirection.rtl. + + Image position calculated by TextPainter is strange. + +- Not support:it won't handle special text when obscureText is true. + +## Special Text + +![](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_text_field/extended_text_field.gif) + +### Create Special Text + +extended text helps to convert your text to special textSpan quickly. + +for example, follwing code show how to create @xxxx special textSpan. + +```dart +class AtText extends SpecialText { + static const String flag = "@"; + final int start; + + /// whether show background for @somebody + final bool showAtBackground; + + AtText(TextStyle textStyle, SpecialTextGestureTapCallback onTap, + {this.showAtBackground: false, this.start}) + : super( + flag, + " ", + textStyle, + ); + + @override + InlineSpan finishText() { + TextStyle textStyle = + this.textStyle?.copyWith(color: Colors.blue, fontSize: 16.0); + + final String atText = toString(); + + return showAtBackground + ? BackgroundTextSpan( + background: Paint()..color = Colors.blue.withOpacity(0.15), + text: atText, + actualText: atText, + start: start, + + ///caret can move into special text + deleteAll: true, + style: textStyle, + recognizer: (TapGestureRecognizer() + ..onTap = () { + if (onTap != null) onTap(atText); + })) + : SpecialTextSpan( + text: atText, + actualText: atText, + start: start, + style: textStyle, + recognizer: (TapGestureRecognizer() + ..onTap = () { + if (onTap != null) onTap(atText); + })); + } +} + +``` + +### SpecialTextSpanBuilder + +create your SpecialTextSpanBuilder + +```dart +class MySpecialTextSpanBuilder extends SpecialTextSpanBuilder { + /// whether show background for @somebody + final bool showAtBackground; + final BuilderType type; + MySpecialTextSpanBuilder( + {this.showAtBackground: false, this.type: BuilderType.extendedText}); + + @override + TextSpan build(String data, {TextStyle textStyle, onTap}) { + var textSpan = super.build(data, textStyle: textStyle, onTap: onTap); + return textSpan; + } + + @override + SpecialText createSpecialText(String flag, + {TextStyle textStyle, SpecialTextGestureTapCallback onTap, int index}) { + if (flag == null || flag == "") return null; + + ///index is end index of start flag, so text start index should be index-(flag.length-1) + if (isStart(flag, AtText.flag)) { + return AtText(textStyle, onTap, + start: index - (AtText.flag.length - 1), + showAtBackground: showAtBackground, + type: type); + } else if (isStart(flag, EmojiText.flag)) { + return EmojiText(textStyle, start: index - (EmojiText.flag.length - 1)); + } else if (isStart(flag, DollarText.flag)) { + return DollarText(textStyle, onTap, + start: index - (DollarText.flag.length - 1), type: type); + } + return null; + } +} +``` + +## Image + +![](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_text_field/extended_text_field_image.gif) + +### ImageSpan + +show inline image by using ImageSpan. + +```dart +ImageSpan( + ImageProvider image, { + Key key, + @required double imageWidth, + @required double imageHeight, + EdgeInsets margin, + int start: 0, + ui.PlaceholderAlignment alignment = ui.PlaceholderAlignment.bottom, + String actualText, + TextBaseline baseline, + TextStyle style, + BoxFit fit: BoxFit.scaleDown, + ImageLoadingBuilder loadingBuilder, + ImageFrameBuilder frameBuilder, + String semanticLabel, + bool excludeFromSemantics = false, + Color color, + BlendMode colorBlendMode, + AlignmentGeometry imageAlignment = Alignment.center, + ImageRepeat repeat = ImageRepeat.noRepeat, + Rect centerSlice, + bool matchTextDirection = false, + bool gaplessPlayback = false, + FilterQuality filterQuality = FilterQuality.low, + }) + +ImageSpan(AssetImage("xxx.jpg"), + imageWidth: size, + imageHeight: size, + margin: EdgeInsets.only(left: 2.0, bottom: 0.0, right: 2.0)); + } +``` + +| parameter | description | default | +| ----------- | ----------------------------------------------------------------------------- | -------- | +| image | The image to display(ImageProvider). | - | +| imageWidth | The width of image(not include margin) | required | +| imageHeight | The height of image(not include margin) | required | +| margin | The margin of image | - | +| actualText | Actual text, take care of it when enable selection,something likes "\[love\]" | '\uFFFC' | +| start | Start index of text,take care of it when enable selection. | 0 | + +### Cache Image + +if you want cache the network image, you can use ExtendedNetworkImageProvider and clear them with clearDiskCachedImages + +import extended_image_library + +```dart +dependencies: + extended_image_library: ^0.1.4 +``` + +```dart +ExtendedNetworkImageProvider( + this.url, { + this.scale = 1.0, + this.headers, + this.cache: false, + this.retries = 3, + this.timeLimit, + this.timeRetry = const Duration(milliseconds: 100), + CancellationToken cancelToken, +}) : assert(url != null), + assert(scale != null), + cancelToken = cancelToken ?? CancellationToken(); +``` + +| parameter | description | default | +| ----------- | ------------------------------------------------------------------------------------- | ------------------- | +| url | The URL from which the image will be fetched. | required | +| scale | The scale to place in the [ImageInfo] object of the image. | 1.0 | +| headers | The HTTP headers that will be used with [HttpClient.get] to fetch image from network. | - | +| cache | whether cache image to local | false | +| retries | the time to retry to request | 3 | +| timeLimit | time limit to request image | - | +| timeRetry | the time duration to retry to request | milliseconds: 100 | +| cancelToken | token to cancel network request | CancellationToken() | + +```dart +/// Clear the disk cache directory then return if it succeed. +/// timespan to compute whether file has expired or not +Future clearDiskCachedImages({Duration duration}) async +``` + +## TextSelectionControls + +![](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_text_field/custom_toolbar.gif) + + +override [ExtendedTextField.extendedContextMenuBuilder] and [TextSelectionControls] to custom your toolbar widget or handle widget + +```dart +const double _kHandleSize = 22.0; + +/// Android Material styled text selection controls. +class MyTextSelectionControls extends TextSelectionControls + with TextSelectionHandleControls { + static Widget defaultContextMenuBuilder( + BuildContext context, ExtendedEditableTextState editableTextState) { + return AdaptiveTextSelectionToolbar.buttonItems( + buttonItems: [ + ...editableTextState.contextMenuButtonItems, + ContextMenuButtonItem( + onPressed: () { + launchUrl( + Uri.parse( + 'mailto:zmtzawqlp@live.com?subject=extended_text_share&body=${editableTextState.textEditingValue.text}', + ), + ); + editableTextState.hideToolbar(true); + editableTextState.textEditingValue + .copyWith(selection: const TextSelection.collapsed(offset: 0)); + }, + type: ContextMenuButtonType.custom, + label: 'like', + ), + ], + anchors: editableTextState.contextMenuAnchors, + ); + // return AdaptiveTextSelectionToolbar.editableText( + // editableTextState: editableTextState, + // ); + } + + /// Returns the size of the Material handle. + @override + Size getHandleSize(double textLineHeight) => + const Size(_kHandleSize, _kHandleSize); + + /// Builder for material-style text selection handles. + @override + Widget buildHandle( + BuildContext context, TextSelectionHandleType type, double textLineHeight, + [VoidCallback? onTap, double? startGlyphHeight, double? endGlyphHeight]) { + final Widget handle = SizedBox( + width: _kHandleSize, + height: _kHandleSize, + child: Image.asset( + 'assets/40.png', + ), + ); + + // [handle] is a circle, with a rectangle in the top left quadrant of that + // circle (an onion pointing to 10:30). We rotate [handle] to point + // straight up or up-right depending on the handle type. + switch (type) { + case TextSelectionHandleType.left: // points up-right + return Transform.rotate( + angle: math.pi / 4.0, + child: handle, + ); + case TextSelectionHandleType.right: // points up-left + return Transform.rotate( + angle: -math.pi / 4.0, + child: handle, + ); + case TextSelectionHandleType.collapsed: // points up + return handle; + } + } + + /// Gets anchor for material-style text selection handles. + /// + /// See [TextSelectionControls.getHandleAnchor]. + @override + Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight, + [double? startGlyphHeight, double? endGlyphHeight]) { + switch (type) { + case TextSelectionHandleType.left: + return const Offset(_kHandleSize, 0); + case TextSelectionHandleType.right: + return Offset.zero; + default: + return const Offset(_kHandleSize / 2, -4); + } + } + + @override + bool canSelectAll(TextSelectionDelegate delegate) { + // Android allows SelectAll when selection is not collapsed, unless + // everything has already been selected. + final TextEditingValue value = delegate.textEditingValue; + return delegate.selectAllEnabled && + value.text.isNotEmpty && + !(value.selection.start == 0 && + value.selection.end == value.text.length); + } +} + +``` + +## WidgetSpan + +![](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_text_field/widget_span.gif) + +support to select and hitTest ExtendedWidgetSpan, you can create any widget in ExtendedTextField. + +```dart +class EmailText extends SpecialText { + final TextEditingController controller; + final int start; + final BuildContext context; + EmailText(TextStyle textStyle, SpecialTextGestureTapCallback onTap, + {this.start, this.controller, this.context, String startFlag}) + : super(startFlag, " ", textStyle, onTap: onTap); + + @override + bool isEnd(String value) { + var index = value.indexOf("@"); + var index1 = value.indexOf("."); + + return index >= 0 && + index1 >= 0 && + index1 > index + 1 && + super.isEnd(value); + } + + @override + InlineSpan finishText() { + final String text = toString(); + + return ExtendedWidgetSpan( + actualText: text, + start: start, + alignment: ui.PlaceholderAlignment.middle, + child: GestureDetector( + child: Padding( + padding: EdgeInsets.only(right: 5.0, top: 2.0, bottom: 2.0), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(5.0)), + child: Container( + padding: EdgeInsets.all(5.0), + color: Colors.orange, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + text.trim(), + //style: textStyle?.copyWith(color: Colors.orange), + ), + SizedBox( + width: 5.0, + ), + InkWell( + child: Icon( + Icons.close, + size: 15.0, + ), + onTap: () { + controller.value = controller.value.copyWith( + text: controller.text + .replaceRange(start, start + text.length, ""), + selection: TextSelection.fromPosition( + TextPosition(offset: start))); + }, + ) + ], + ), + )), + ), + onTap: () { + showDialog( + context: context, + barrierDismissible: true, + builder: (c) { + TextEditingController textEditingController = + TextEditingController()..text = text.trim(); + return Column( + children: [ + Expanded( + child: Container(), + ), + Material( + child: Padding( + padding: EdgeInsets.all(10.0), + child: TextField( + controller: textEditingController, + decoration: InputDecoration( + suffixIcon: FlatButton( + child: Text("OK"), + onPressed: () { + controller.value = controller.value.copyWith( + text: controller.text.replaceRange( + start, + start + text.length, + textEditingController.text + " "), + selection: TextSelection.fromPosition( + TextPosition( + offset: start + + (textEditingController.text + " ") + .length))); + + Navigator.pop(context); + }, + )), + ), + )), + Expanded( + child: Container(), + ) + ], + ); + }); + }, + ), + deleteAll: true, + ); + } +} +``` + +## NoSystemKeyboard + +support to prevent system keyboard show without any code intrusion for [ExtendedTextField] or [TextField]. + +### TextInputBindingMixin + +we prevent system keyboard show by stop Flutter Framework send `TextInput.show` message to Flutter Engine. + +you can use [TextInputBinding] directly. + +``` dart +void main() { + TextInputBinding(); + runApp(const MyApp()); +} +``` + +or if you have other `binding` you can do as following. + +``` dart + class YourBinding extends WidgetsFlutterBinding with TextInputBindingMixin,YourBindingMixin { + } + + void main() { + YourBinding(); + runApp(const MyApp()); + } +``` + +or you need to override `ignoreTextInputShow`, you can do as following. + +``` dart + class YourBinding extends TextInputBinding { + @override + // ignore: unnecessary_overrides + bool ignoreTextInputShow() { + // you can override it base on your case + // if NoKeyboardFocusNode is not enough + return super.ignoreTextInputShow(); + } + } + + void main() { + YourBinding(); + runApp(const MyApp()); + } +``` + +### TextInputFocusNode + +you should pass the [TextInputFocusNode] into [ExtendedTextField] or [TextField]. + +``` dart +final TextInputFocusNode _focusNode = TextInputFocusNode(); + + @override + Widget build(BuildContext context) { + return ExtendedTextField( + // request keyboard if need + focusNode: _focusNode..debugLabel = 'ExtendedTextField', + ); + } + + @override + Widget build(BuildContext context) { + return TextField( + // request keyboard if need + focusNode: _focusNode..debugLabel = 'CustomTextField', + ); + } +``` + +we prevent system keyboard show base on current focus is [TextInputFocusNode] and `ignoreSystemKeyboardShow` is true。 + +``` dart + final FocusNode? focus = FocusManager.instance.primaryFocus; + if (focus != null && + focus is TextInputFocusNode && + focus.ignoreSystemKeyboardShow) { + return true; + } + +### CustomKeyboard + +show/hide your custom keyboard on [TextInputFocusNode] focus is changed. + +if your custom keyboard can be close without unFocus, you need also handle +show custom keyboard when [ExtendedTextField] or [TextField] `onTap`. + +``` dart + @override + void initState() { + super.initState(); + _focusNode.addListener(_handleFocusChanged); + } + + void _onTextFiledTap() { + if (_bottomSheetController == null) { + _handleFocusChanged(); + } + } + + void _handleFocusChanged() { + if (_focusNode.hasFocus) { + // just demo, you can define your custom keyboard as you want + _bottomSheetController = showBottomSheet( + context: FocusManager.instance.primaryFocus!.context!, + // set false, if don't want to drag to close custom keyboard + enableDrag: true, + builder: (BuildContext b) { + // your custom keyboard + return Container(); + }); + // maybe drag close + _bottomSheetController?.closed.whenComplete(() { + _bottomSheetController = null; + }); + } else { + _bottomSheetController?.close(); + _bottomSheetController = null; + } + } + + @override + void dispose() { + _focusNode.removeListener(_handleFocusChanged); + super.dispose(); + } +``` + + +see [Full Demo](https://github.com/fluttercandies/extended_text_field/tree/master/example/lib/pages/simple/no_keyboard.dart) \ No newline at end of file diff --git a/local_packages/extended_text_field-16.0.2/analysis_options.yaml b/local_packages/extended_text_field-16.0.2/analysis_options.yaml new file mode 100644 index 0000000..47879b9 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/analysis_options.yaml @@ -0,0 +1,203 @@ +# Specify analysis options. +# +# Until there are meta linter rules, each desired lint must be explicitly enabled. +# See: https://github.com/dart-lang/linter/issues/288 +# +# For a list of lints, see: http://dart-lang.github.io/linter/lints/ +# See the configuration guide for more +# https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer +# +# There are other similar analysis options files in the flutter repos, +# which should be kept in sync with this file: +# +# - analysis_options.yaml (this file) +# - packages/flutter/lib/analysis_options_user.yaml +# - https://github.com/flutter/plugins/blob/master/analysis_options.yaml +# - https://github.com/flutter/engine/blob/master/analysis_options.yaml +# +# This file contains the analysis options used by Flutter tools, such as IntelliJ, +# Android Studio, and the `flutter analyze` command. + +analyzer: + errors: + # treat missing required parameters as a warning (not a hint) + missing_required_param: warning + # treat missing returns as a warning (not a hint) + missing_return: warning + # allow having TODOs in the code + todo: ignore + # Ignore analyzer hints for updating pubspecs when using Future or + # Stream and not importing dart:async + # Please see https://github.com/flutter/flutter/pull/24528 for details. + # sdk_version_async_exported_from_core: ignore + # exclude: + # - "bin/cache/**" + # # the following two are relative to the stocks example and the flutter package respectively + # # see https://github.com/dart-lang/sdk/issues/28463 + # - "lib/i18n/messages_*.dart" + # - "lib/src/http/**" + +linter: + rules: + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # https://github.com/dart-lang/linter/blob/master/example/all.yaml + - always_declare_return_types + - always_put_control_body_on_new_line + # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 + # - always_require_non_null_named_parameters + - always_specify_types + - annotate_overrides + # - avoid_annotating_with_dynamic # conflicts with always_specify_types + # - avoid_as # required for implicit-casts: true + - avoid_bool_literals_in_conditional_expressions + # - avoid_catches_without_on_clauses # we do this commonly + # - avoid_catching_errors # we do this commonly + - avoid_classes_with_only_static_members + # - avoid_double_and_int_checks # only useful when targeting JS runtime + - avoid_empty_else + # - avoid_equals_and_hash_code_on_mutable_classes # not yet tested + - avoid_field_initializers_in_const_classes + - avoid_function_literals_in_foreach_calls + # - avoid_implementing_value_types # not yet tested + - avoid_init_to_null + # - avoid_js_rounded_ints # only useful when targeting JS runtime + - avoid_null_checks_in_equality_operators + # - avoid_positional_boolean_parameters # not yet tested + # - avoid_print # not yet tested + # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) + # - avoid_redundant_argument_values # not yet tested + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + # - avoid_returning_null # there are plenty of valid reasons to return null + # - avoid_returning_null_for_future # not yet tested + - avoid_returning_null_for_void + # - avoid_returning_this # there are plenty of valid reasons to return this + # - avoid_setters_without_getters # not yet tested + # - avoid_shadowing_type_parameters # not yet tested + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_types_as_parameter_names + # - avoid_types_on_closure_parameters # conflicts with always_specify_types + # - avoid_unnecessary_containers # not yet tested + - avoid_unused_constructor_parameters + - avoid_void_async + # - avoid_web_libraries_in_flutter # not yet tested + - await_only_futures + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + # - cascade_invocations # not yet tested + # - close_sinks # not reliable enough + # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 + # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 + - control_flow_in_finally + # - curly_braces_in_flow_control_structures # not yet tested + # - diagnostic_describe_all_properties # not yet tested + - directives_ordering + - empty_catches + - empty_constructor_bodies + - empty_statements + # - file_names # not yet tested + - flutter_style_todos + - hash_and_equals + - implementation_imports + # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 + # - iterable_contains_unrelated_type + # - join_return_with_assignment # not yet tested + - library_names + - library_prefixes + # - lines_longer_than_80_chars # not yet tested + # - list_remove_unrelated_type + # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 + # - missing_whitespace_between_adjacent_strings # not yet tested + - no_adjacent_strings_in_list + - no_duplicate_case_values + # - no_logic_in_create_state # not yet tested + # - no_runtimeType_toString # not yet tested + - non_constant_identifier_names + # - null_closures # not yet tested + # - omit_local_variable_types # opposite of always_specify_types + # - one_member_abstracts # too many false positives + # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + # - parameter_assignments # we do this commonly + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + # - prefer_asserts_with_message # not yet tested + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + # - prefer_constructors_over_static_methods # not yet tested + - prefer_contains + # - prefer_double_quotes # opposite of prefer_single_quotes + # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + # - prefer_function_declarations_over_variables # not yet tested + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + # - prefer_int_literals # not yet tested + # - prefer_interpolation_to_compose_strings # not yet tested + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + # - prefer_mixin # https://github.com/dart-lang/language/issues/32 + # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 + # - prefer_relative_imports # not yet tested + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + # - provide_deprecation_message # not yet tested + # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml + - recursive_getters + - slash_for_doc_comments + # - sort_child_properties_last # not yet tested + - sort_constructors_first + # - sort_pub_dependencies + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + # - type_annotate_public_apis # subset of always_specify_types + - type_init_formals + # - unawaited_futures # too many false positives + # - unnecessary_await_in_return # not yet tested + - unnecessary_brace_in_string_interps + - unnecessary_const + # - unnecessary_final # conflicts with prefer_final_locals + - unnecessary_getters_setters + # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_statements + - unnecessary_string_interpolations + - unnecessary_this + - unrelated_type_equality_checks + # - unsafe_html # not yet tested + - use_full_hex_values_for_flutter_colors + # - use_function_type_syntax_for_parameters # not yet tested + # - use_key_in_widget_constructors # not yet tested + - use_rethrow_when_possible + # - use_setters_to_change_properties # not yet tested + # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 + # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review + - valid_regexps + - void_checks diff --git a/local_packages/extended_text_field-16.0.2/example/README.md b/local_packages/extended_text_field-16.0.2/example/README.md new file mode 100644 index 0000000..99ce5c8 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter application. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/local_packages/extended_text_field-16.0.2/example/analysis_options.yaml b/local_packages/extended_text_field-16.0.2/example/analysis_options.yaml new file mode 100644 index 0000000..04e68ba --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/analysis_options.yaml @@ -0,0 +1,203 @@ +# Specify analysis options. +# +# Until there are meta linter rules, each desired lint must be explicitly enabled. +# See: https://github.com/dart-lang/linter/issues/288 +# +# For a list of lints, see: http://dart-lang.github.io/linter/lints/ +# See the configuration guide for more +# https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer +# +# There are other similar analysis options files in the flutter repos, +# which should be kept in sync with this file: +# +# - analysis_options.yaml (this file) +# - packages/flutter/lib/analysis_options_user.yaml +# - https://github.com/flutter/plugins/blob/master/analysis_options.yaml +# - https://github.com/flutter/engine/blob/master/analysis_options.yaml +# +# This file contains the analysis options used by Flutter tools, such as IntelliJ, +# Android Studio, and the `flutter analyze` command. + +analyzer: + errors: + # treat missing required parameters as a warning (not a hint) + missing_required_param: warning + # treat missing returns as a warning (not a hint) + missing_return: warning + # allow having TODOs in the code + todo: ignore + # Ignore analyzer hints for updating pubspecs when using Future or + # Stream and not importing dart:async + # Please see https://github.com/flutter/flutter/pull/24528 for details. + # sdk_version_async_exported_from_core: ignore + # exclude: + # - "bin/cache/**" + # # the following two are relative to the stocks example and the flutter package respectively + # # see https://github.com/dart-lang/sdk/issues/28463 + # - "lib/i18n/messages_*.dart" + # - "lib/src/http/**" + +linter: + rules: + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # https://github.com/dart-lang/linter/blob/master/example/all.yaml + - always_declare_return_types + - always_put_control_body_on_new_line + # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 + # - always_require_non_null_named_parameters + - always_specify_types + - annotate_overrides + # - avoid_annotating_with_dynamic # conflicts with always_specify_types + # - avoid_as # required for implicit-casts: true + - avoid_bool_literals_in_conditional_expressions + # - avoid_catches_without_on_clauses # we do this commonly + # - avoid_catching_errors # we do this commonly + - avoid_classes_with_only_static_members + # - avoid_double_and_int_checks # only useful when targeting JS runtime + - avoid_empty_else + # - avoid_equals_and_hash_code_on_mutable_classes # not yet tested + - avoid_field_initializers_in_const_classes + - avoid_function_literals_in_foreach_calls + # - avoid_implementing_value_types # not yet tested + - avoid_init_to_null + # - avoid_js_rounded_ints # only useful when targeting JS runtime + - avoid_null_checks_in_equality_operators + # - avoid_positional_boolean_parameters # not yet tested + # - avoid_print # not yet tested + # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) + # - avoid_redundant_argument_values # not yet tested + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + # - avoid_returning_null # there are plenty of valid reasons to return null + # - avoid_returning_null_for_future # not yet tested + - avoid_returning_null_for_void + # - avoid_returning_this # there are plenty of valid reasons to return this + # - avoid_setters_without_getters # not yet tested + # - avoid_shadowing_type_parameters # not yet tested + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_types_as_parameter_names + # - avoid_types_on_closure_parameters # conflicts with always_specify_types + # - avoid_unnecessary_containers # not yet tested + - avoid_unused_constructor_parameters + - avoid_void_async + # - avoid_web_libraries_in_flutter # not yet tested + - await_only_futures + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + # - cascade_invocations # not yet tested + # - close_sinks # not reliable enough + # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 + # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 + - control_flow_in_finally + # - curly_braces_in_flow_control_structures # not yet tested + # - diagnostic_describe_all_properties # not yet tested + - directives_ordering + - empty_catches + - empty_constructor_bodies + - empty_statements + # - file_names # not yet tested + - flutter_style_todos + - hash_and_equals + - implementation_imports + # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 + # - iterable_contains_unrelated_type + # - join_return_with_assignment # not yet tested + - library_names + - library_prefixes + # - lines_longer_than_80_chars # not yet tested + # - list_remove_unrelated_type + # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 + # - missing_whitespace_between_adjacent_strings # not yet tested + - no_adjacent_strings_in_list + - no_duplicate_case_values + # - no_logic_in_create_state # not yet tested + # - no_runtimeType_toString # not yet tested + - non_constant_identifier_names + # - null_closures # not yet tested + # - omit_local_variable_types # opposite of always_specify_types + # - one_member_abstracts # too many false positives + # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + # - parameter_assignments # we do this commonly + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + # - prefer_asserts_with_message # not yet tested + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + # - prefer_constructors_over_static_methods # not yet tested + - prefer_contains + # - prefer_double_quotes # opposite of prefer_single_quotes + # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + # - prefer_function_declarations_over_variables # not yet tested + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + # - prefer_int_literals # not yet tested + # - prefer_interpolation_to_compose_strings # not yet tested + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + # - prefer_mixin # https://github.com/dart-lang/language/issues/32 + # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 + # - prefer_relative_imports # not yet tested + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + # - provide_deprecation_message # not yet tested + # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml + - recursive_getters + - slash_for_doc_comments + #- sort_child_properties_last # not yet tested + - sort_constructors_first + #- sort_pub_dependencies + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + # - type_annotate_public_apis # subset of always_specify_types + - type_init_formals + # - unawaited_futures # too many false positives + # - unnecessary_await_in_return # not yet tested + - unnecessary_brace_in_string_interps + - unnecessary_const + # - unnecessary_final # conflicts with prefer_final_locals + - unnecessary_getters_setters + # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_statements + - unnecessary_string_interpolations + - unnecessary_this + - unrelated_type_equality_checks + # - unsafe_html # not yet tested + - use_full_hex_values_for_flutter_colors + # - use_function_type_syntax_for_parameters # not yet tested + # - use_key_in_widget_constructors # not yet tested + - use_rethrow_when_possible + # - use_setters_to_change_properties # not yet tested + # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 + # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review + - valid_regexps + - void_checks diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/build.gradle b/local_packages/extended_text_field-16.0.2/example/android/app/build.gradle new file mode 100644 index 0000000..118ee1d --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/android/app/build.gradle @@ -0,0 +1,67 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "com.example.example" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies {} diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/src/debug/AndroidManifest.xml b/local_packages/extended_text_field-16.0.2/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/src/main/AndroidManifest.xml b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..19b862e --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 0000000..e793a00 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/drawable-v21/launch_background.xml b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/drawable/launch_background.xml b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/values-night/styles.xml b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/values/styles.xml b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/android/app/src/profile/AndroidManifest.xml b/local_packages/extended_text_field-16.0.2/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/android/build.gradle b/local_packages/extended_text_field-16.0.2/example/android/build.gradle new file mode 100644 index 0000000..e83fb5d --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/android/build.gradle @@ -0,0 +1,30 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/local_packages/extended_text_field-16.0.2/example/android/gradle.properties b/local_packages/extended_text_field-16.0.2/example/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/local_packages/extended_text_field-16.0.2/example/android/gradle/wrapper/gradle-wrapper.properties b/local_packages/extended_text_field-16.0.2/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/local_packages/extended_text_field-16.0.2/example/android/settings.gradle b/local_packages/extended_text_field-16.0.2/example/android/settings.gradle new file mode 100644 index 0000000..7cd7128 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/android/settings.gradle @@ -0,0 +1,29 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false +} + +include ":app" diff --git a/local_packages/extended_text_field-16.0.2/example/assets/1.png b/local_packages/extended_text_field-16.0.2/example/assets/1.png new file mode 100644 index 0000000..7781b80 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/1.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/10.png b/local_packages/extended_text_field-16.0.2/example/assets/10.png new file mode 100644 index 0000000..abce714 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/10.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/11.png b/local_packages/extended_text_field-16.0.2/example/assets/11.png new file mode 100644 index 0000000..23bd18e Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/11.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/12.png b/local_packages/extended_text_field-16.0.2/example/assets/12.png new file mode 100644 index 0000000..e27a414 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/12.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/13.png b/local_packages/extended_text_field-16.0.2/example/assets/13.png new file mode 100644 index 0000000..a64a750 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/13.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/14.png b/local_packages/extended_text_field-16.0.2/example/assets/14.png new file mode 100644 index 0000000..8de24cc Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/14.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/15.png b/local_packages/extended_text_field-16.0.2/example/assets/15.png new file mode 100644 index 0000000..65b942c Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/15.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/16.png b/local_packages/extended_text_field-16.0.2/example/assets/16.png new file mode 100644 index 0000000..d94736e Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/16.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/17.png b/local_packages/extended_text_field-16.0.2/example/assets/17.png new file mode 100644 index 0000000..5854a7f Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/17.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/18.png b/local_packages/extended_text_field-16.0.2/example/assets/18.png new file mode 100644 index 0000000..5d68900 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/18.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/19.png b/local_packages/extended_text_field-16.0.2/example/assets/19.png new file mode 100644 index 0000000..c051967 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/19.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/2.png b/local_packages/extended_text_field-16.0.2/example/assets/2.png new file mode 100644 index 0000000..73101eb Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/2.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/20.png b/local_packages/extended_text_field-16.0.2/example/assets/20.png new file mode 100644 index 0000000..446e9e3 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/20.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/21.png b/local_packages/extended_text_field-16.0.2/example/assets/21.png new file mode 100644 index 0000000..1c70812 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/21.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/22.png b/local_packages/extended_text_field-16.0.2/example/assets/22.png new file mode 100644 index 0000000..d8773ed Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/22.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/23.png b/local_packages/extended_text_field-16.0.2/example/assets/23.png new file mode 100644 index 0000000..3645445 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/23.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/24.png b/local_packages/extended_text_field-16.0.2/example/assets/24.png new file mode 100644 index 0000000..bb412c2 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/24.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/25.png b/local_packages/extended_text_field-16.0.2/example/assets/25.png new file mode 100644 index 0000000..78e44d6 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/25.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/26.png b/local_packages/extended_text_field-16.0.2/example/assets/26.png new file mode 100644 index 0000000..a27eab4 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/26.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/27.png b/local_packages/extended_text_field-16.0.2/example/assets/27.png new file mode 100644 index 0000000..188eca3 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/27.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/28.png b/local_packages/extended_text_field-16.0.2/example/assets/28.png new file mode 100644 index 0000000..bf0d182 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/28.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/29.png b/local_packages/extended_text_field-16.0.2/example/assets/29.png new file mode 100644 index 0000000..2f05e7f Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/29.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/3.png b/local_packages/extended_text_field-16.0.2/example/assets/3.png new file mode 100644 index 0000000..17b7eaa Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/3.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/30.png b/local_packages/extended_text_field-16.0.2/example/assets/30.png new file mode 100644 index 0000000..27b3145 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/30.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/31.png b/local_packages/extended_text_field-16.0.2/example/assets/31.png new file mode 100644 index 0000000..94b9916 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/31.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/32.png b/local_packages/extended_text_field-16.0.2/example/assets/32.png new file mode 100644 index 0000000..b6c37ce Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/32.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/33.png b/local_packages/extended_text_field-16.0.2/example/assets/33.png new file mode 100644 index 0000000..516b074 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/33.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/34.png b/local_packages/extended_text_field-16.0.2/example/assets/34.png new file mode 100644 index 0000000..3a5a90c Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/34.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/35.png b/local_packages/extended_text_field-16.0.2/example/assets/35.png new file mode 100644 index 0000000..9a8d5fd Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/35.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/36.png b/local_packages/extended_text_field-16.0.2/example/assets/36.png new file mode 100644 index 0000000..8cc4568 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/36.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/37.png b/local_packages/extended_text_field-16.0.2/example/assets/37.png new file mode 100644 index 0000000..dc67aa9 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/37.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/38.png b/local_packages/extended_text_field-16.0.2/example/assets/38.png new file mode 100644 index 0000000..efaaad0 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/38.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/39.png b/local_packages/extended_text_field-16.0.2/example/assets/39.png new file mode 100644 index 0000000..5f5ee12 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/39.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/4.png b/local_packages/extended_text_field-16.0.2/example/assets/4.png new file mode 100644 index 0000000..3262ca9 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/4.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/40.png b/local_packages/extended_text_field-16.0.2/example/assets/40.png new file mode 100644 index 0000000..9b65b13 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/40.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/41.png b/local_packages/extended_text_field-16.0.2/example/assets/41.png new file mode 100644 index 0000000..b8807ac Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/41.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/42.png b/local_packages/extended_text_field-16.0.2/example/assets/42.png new file mode 100644 index 0000000..09acb70 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/42.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/43.png b/local_packages/extended_text_field-16.0.2/example/assets/43.png new file mode 100644 index 0000000..97f303a Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/43.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/44.png b/local_packages/extended_text_field-16.0.2/example/assets/44.png new file mode 100644 index 0000000..0797d3e Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/44.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/45.png b/local_packages/extended_text_field-16.0.2/example/assets/45.png new file mode 100644 index 0000000..9954468 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/45.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/46.png b/local_packages/extended_text_field-16.0.2/example/assets/46.png new file mode 100644 index 0000000..7332b7d Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/46.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/47.png b/local_packages/extended_text_field-16.0.2/example/assets/47.png new file mode 100644 index 0000000..da7f13c Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/47.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/48.png b/local_packages/extended_text_field-16.0.2/example/assets/48.png new file mode 100644 index 0000000..497fc38 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/48.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/5.png b/local_packages/extended_text_field-16.0.2/example/assets/5.png new file mode 100644 index 0000000..751f91a Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/5.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/6.png b/local_packages/extended_text_field-16.0.2/example/assets/6.png new file mode 100644 index 0000000..a420b4e Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/6.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/7.png b/local_packages/extended_text_field-16.0.2/example/assets/7.png new file mode 100644 index 0000000..e19d8c7 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/7.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/8.png b/local_packages/extended_text_field-16.0.2/example/assets/8.png new file mode 100644 index 0000000..c5a0b43 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/8.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/9.png b/local_packages/extended_text_field-16.0.2/example/assets/9.png new file mode 100644 index 0000000..dea81b3 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/9.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/assets/flutter_candies_logo.png b/local_packages/extended_text_field-16.0.2/example/assets/flutter_candies_logo.png new file mode 100644 index 0000000..53ace5a Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/assets/flutter_candies_logo.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ff_annotation_route_commands b/local_packages/extended_text_field-16.0.2/example/ff_annotation_route_commands new file mode 100644 index 0000000..ebf8ab2 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ff_annotation_route_commands @@ -0,0 +1 @@ +-s \ No newline at end of file diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Flutter/AppFrameworkInfo.plist b/local_packages/extended_text_field-16.0.2/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..4f8d4d2 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Flutter/Debug.xcconfig b/local_packages/extended_text_field-16.0.2/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..e8efba1 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Flutter/Release.xcconfig b/local_packages/extended_text_field-16.0.2/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..399e934 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Podfile b/local_packages/extended_text_field-16.0.2/example/ios/Podfile new file mode 100644 index 0000000..88359b2 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Podfile.lock b/local_packages/extended_text_field-16.0.2/example/ios/Podfile.lock new file mode 100644 index 0000000..377a985 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - Flutter (1.0.0) + - url_launcher_ios (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + +SPEC CHECKSUMS: + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2 + +PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 + +COCOAPODS: 1.11.2 diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/project.pbxproj b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f5c19d1 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,566 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 12DF17661E7D053967D9F0A5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 077B3BB1790B19B0B9B3DA35 /* Pods_Runner.framework */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 077B3BB1790B19B0B9B3DA35 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B3394F8EB42DB1DE0424DE03 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + D70E638480C55D8480B56A38 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + FC67FABFE76D4AE404256943 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 12DF17661E7D053967D9F0A5 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 32D6548EE3BBF9FC104A3D3B /* Frameworks */ = { + isa = PBXGroup; + children = ( + 077B3BB1790B19B0B9B3DA35 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 53FC81DD4D77A1C70BDD5FBD /* Pods */ = { + isa = PBXGroup; + children = ( + FC67FABFE76D4AE404256943 /* Pods-Runner.debug.xcconfig */, + D70E638480C55D8480B56A38 /* Pods-Runner.release.xcconfig */, + B3394F8EB42DB1DE0424DE03 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 53FC81DD4D77A1C70BDD5FBD /* Pods */, + 32D6548EE3BBF9FC104A3D3B /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 075D337C84B38FC7530C1FEC /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + B5C945045794C5F8A8418301 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 075D337C84B38FC7530C1FEC /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + B5C945045794C5F8A8418301 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..3db53b6 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/AppDelegate.swift b/local_packages/extended_text_field-16.0.2/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Base.lproj/Main.storyboard b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Info.plist b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Info.plist new file mode 100644 index 0000000..4f68a2c --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/local_packages/extended_text_field-16.0.2/example/ios/Runner/Runner-Bridging-Header.h b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/local_packages/extended_text_field-16.0.2/example/lib/common/button.dart b/local_packages/extended_text_field-16.0.2/example/lib/common/button.dart new file mode 100644 index 0000000..69e021b --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/common/button.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; + +class NumberButton extends StatelessWidget { + const NumberButton({ + Key? key, + required this.number, + required this.insertText, + }) : super(key: key); + final int number; + final Function(String text) insertText; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + insertText('$number'); + }, + child: Container( + margin: const EdgeInsets.all(5), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + boxShadow: const [ + BoxShadow( + color: Colors.black12, + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + child: Text( + '$number', + ), + ), + ); + } +} + +class CustomButton extends StatelessWidget { + const CustomButton({ + Key? key, + required this.child, + required this.onTap, + }) : super(key: key); + final Widget child; + final GestureTapCallback onTap; + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + margin: const EdgeInsets.all(5), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + boxShadow: const [ + BoxShadow( + color: Colors.black12, + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + child: child, + ), + ); + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/common/toggle_button.dart b/local_packages/extended_text_field-16.0.2/example/lib/common/toggle_button.dart new file mode 100644 index 0000000..737b9de --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/common/toggle_button.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +class ToggleButton extends StatefulWidget { + const ToggleButton({ + this.activeChanged, + this.active = false, + super.key, + required this.builder, + }); + + final bool active; + final ValueChanged? activeChanged; + final Widget Function(bool active) builder; + @override + State createState() => _ToggleButtonState(); +} + +class _ToggleButtonState extends State { + bool _active = false; + + @override + void initState() { + _active = widget.active; + super.initState(); + } + + @override + void didUpdateWidget(ToggleButton oldWidget) { + _active = widget.active; + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + setState(() { + _active = !_active; + widget.activeChanged?.call(_active); + }); + }, + child: Padding( + padding: const EdgeInsets.only(left: 20.0, top: 10.0, bottom: 10.0), + child: widget.builder(_active), + ), + ); + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/data/mock_data.dart b/local_packages/extended_text_field-16.0.2/example/lib/data/mock_data.dart new file mode 100644 index 0000000..8c2ff10 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/data/mock_data.dart @@ -0,0 +1,50395 @@ +// ignore_for_file: always_specify_types + +import 'tu_chong_source.dart'; + +// ignore_for_file: implicit_dynamic_list_literal,implicit_dynamic_map_literal +Map _mock = { + 'counts': 316, + 'feedList': [ + { + 'author_id': '2789905', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '归期/\n\n摄影:给给Eru\n出镜:一折', + 'created_at': '', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '深圳小清新摄影小组', + '人像爱好者', + '古装摄影圈子', + '人像写真摄影约拍', + '我要上开屏', + '分享神仙颜值', + '冷色调人像', + '驚鴻·一眸' + ], + 'excerpt': '归期/\n\n摄影:给给Eru\n出镜:一折', + 'favorite_list_prefix': [], + 'favorites': 42, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 302304041, + 'img_id_str': '302304041', + 'title': '001', + 'user_id': 2789905, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 627230197, + 'img_id_str': '627230197', + 'title': '001', + 'user_id': 2789905, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 129943371, + 'img_id_str': '129943371', + 'title': '001', + 'user_id': 2789905, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 629983027, + 'img_id_str': '629983027', + 'title': '001', + 'user_id': 2789905, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 480954067, + 'img_id_str': '480954067', + 'title': '001', + 'user_id': 2789905, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 614450970, + 'img_id_str': '614450970', + 'title': '001', + 'user_id': 2789905, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 154781482, + 'img_id_str': '154781482', + 'title': '001', + 'user_id': 2789905, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 524928648, + 'img_id_str': '524928648', + 'title': '001', + 'user_id': 2789905, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 423413281, + 'img_id_str': '423413281', + 'title': '001', + 'user_id': 2789905, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3508, + 'img_id': 462931952, + 'img_id_str': '462931952', + 'title': '001', + 'user_id': 2789905, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 628147833, + 'img_id_str': '628147833', + 'title': '001', + 'user_id': 2789905, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 400082871, + 'img_id_str': '400082871', + 'title': '001', + 'user_id': 2789905, + 'width': 4000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '01月19日', + 'post_id': 61835188, + 'published_at': '2020-01-19 12:01:24', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '7f35fa915780a55900b9d37f03996a40', + 'shares': 3, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 616, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2789905_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '给给Eru', + 'site_id': '2789905', + 'type': 'user', + 'url': 'https://tuchong.com/2789905/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2789905', + 'sites': [], + 'tags': ['深圳小清新摄影小组', '人像爱好者', '古装摄影圈子', '人像写真摄影约拍', '我要上开屏', '分享神仙颜值'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2789905/61835188/', + 'views': 1330 + }, + { + 'author_id': '999799', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 37, + 'content': + '出镜:@鳗鱼霏儿 (https://www.weibo.com/u/1728752564)\n摄影:@歌罢海西流 (https://www.weibo.com/u/2281254862)\n游戏官博:@明日方舟Arknights \n漫展主办方:@中国国际漫画节动漫游戏展', + 'created_at': '', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': + '出镜:@鳗鱼霏儿 (https://www.weibo.com/u/1728752564)\n摄影:@歌罢海西流 (https://www.weibo.com/u/2281254862)\n游戏官博:@明日方舟Arknights \n漫展主办方:@中国国际漫画节动漫游戏展', + 'favorite_list_prefix': [], + 'favorites': 1285, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4016, + 'img_id': 180335429, + 'img_id_str': '180335429', + 'title': '', + 'user_id': 999799, + 'width': 6016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 653047009, + 'img_id_str': '653047009', + 'title': '', + 'user_id': 999799, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 398898247, + 'img_id_str': '398898247', + 'title': '', + 'user_id': 999799, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5395, + 'img_id': 244823034, + 'img_id_str': '244823034', + 'title': '', + 'user_id': 999799, + 'width': 3602 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5650, + 'img_id': 389854263, + 'img_id_str': '389854263', + 'title': '', + 'user_id': 999799, + 'width': 3772 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5780, + 'img_id': 306360893, + 'img_id_str': '306360893', + 'title': '', + 'user_id': 999799, + 'width': 3858 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5491, + 'img_id': 45462366, + 'img_id_str': '45462366', + 'title': '', + 'user_id': 999799, + 'width': 3666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5831, + 'img_id': 84063617, + 'img_id_str': '84063617', + 'title': '', + 'user_id': 999799, + 'width': 3892 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5665, + 'img_id': 436843148, + 'img_id_str': '436843148', + 'title': '', + 'user_id': 999799, + 'width': 3782 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '28', + 'passed_time': '2019年10月16日', + 'post_id': 56053420, + 'published_at': '2019-10-16 16:01:59', + 'recommend': true, + 'recom_type': '热门', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '7f35fa915780a55900b9d37f03996a40', + 'shares': 33, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 6436, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_999799_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '歌罢', + 'site_id': '999799', + 'type': 'user', + 'url': 'https://tuchong.com/999799/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '999799', + 'sites': [], + 'tags': ['Cosplay', '人像', '少女', '85mm', '美腿', '尼康'], + 'title': '明日方舟cosplay', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/999799/56053420/', + 'views': 69556 + }, + { + 'author_id': '1030747', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 279, + 'content': + '历时5个月,以奇特的拍摄和处理手法展现了株洲的网红地标,恍如进入了「盗梦空间」,谨以此组照片献给我热爱的祖国,我热爱的城市!', + 'created_at': '', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['寻找「城市秘境」', '第三届京东摄影金像奖', '2019你最满意的照片'], + 'excerpt': + '历时5个月,以奇特的拍摄和处理手法展现了株洲的网红地标,恍如进入了「盗梦空间」,谨以此组照片献给我热爱的祖国,我热爱的城市!', + 'favorite_list_prefix': [], + 'favorites': 3494, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 55290291, + 'img_id_str': '55290291', + 'title': '', + 'user_id': 1030747, + 'width': 1217 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 127248519, + 'img_id_str': '127248519', + 'title': '', + 'user_id': 1030747, + 'width': 1245 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 424585238, + 'img_id_str': '424585238', + 'title': '', + 'user_id': 1030747, + 'width': 1304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 469674195, + 'img_id_str': '469674195', + 'title': '', + 'user_id': 1030747, + 'width': 1260 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 311863407, + 'img_id_str': '311863407', + 'title': '', + 'user_id': 1030747, + 'width': 1282 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 514697201, + 'img_id_str': '514697201', + 'title': '', + 'user_id': 1030747, + 'width': 1206 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 277325761, + 'img_id_str': '277325761', + 'title': '', + 'user_id': 1030747, + 'width': 1271 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 461875423, + 'img_id_str': '461875423', + 'title': '', + 'user_id': 1030747, + 'width': 1259 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 203794226, + 'img_id_str': '203794226', + 'title': '', + 'user_id': 1030747, + 'width': 1310 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '252', + 'passed_time': '2019年09月26日', + 'post_id': 53655560, + 'published_at': '2019-09-26 22:05:01', + 'recommend': true, + 'recom_type': '热门', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '13', + 'rqt_id': '7f35fa915780a55900b9d37f03996a40', + 'shares': 247, + 'site': { + 'description': '资深建筑摄影师', + 'domain': '', + 'followers': 4732, + 'has_everphoto_note': false, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1030747_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '石彦科-YANKEE', + 'site_id': '1030747', + 'type': 'user', + 'url': 'https://tuchong.com/1030747/', + 'verification_list': [ + {'verification_reason': '资深建筑摄影师', 'verification_type': 12} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1030747', + 'sites': [], + 'tags': ['寻找「城市秘境」', '第三届京东摄影金像奖', '2019你最满意的照片', '株洲', 'JD看城市', '北京汽车'], + 'title': '盗梦之城——株洲', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1030747/53655560/', + 'views': 144209 + }, + { + 'author_id': '3229117', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 10, + 'content': '我能看见大海的颜色\n我看不清心事', + 'created_at': '', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '极简主义,少即是多', + '最美肖像照', + '有温度的人像', + '济南约拍圈', + '人像写真摄影约拍', + '咔了个图', + '高颜值女神聚集地', + '人像爱好者', + '驚鴻·一眸', + '分享神仙颜值' + ], + 'excerpt': '我能看见大海的颜色\n我看不清心事', + 'favorite_list_prefix': [], + 'favorites': 213, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 132171098, + 'img_id_str': '132171098', + 'title': '001', + 'user_id': 3229117, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 496813711, + 'img_id_str': '496813711', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 55625060, + 'img_id_str': '55625060', + 'title': '001', + 'user_id': 3229117, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 295224561, + 'img_id_str': '295224561', + 'title': '001', + 'user_id': 3229117, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 44353101, + 'img_id_str': '44353101', + 'title': '001', + 'user_id': 3229117, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 584369944, + 'img_id_str': '584369944', + 'title': '001', + 'user_id': 3229117, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 478921963, + 'img_id_str': '478921963', + 'title': '001', + 'user_id': 3229117, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 598001081, + 'img_id_str': '598001081', + 'title': '001', + 'user_id': 3229117, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 95405724, + 'img_id_str': '95405724', + 'title': '001', + 'user_id': 3229117, + 'width': 3000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '10', + 'passed_time': '01月07日', + 'post_id': 61478978, + 'published_at': '2020-01-07 10:24:35', + 'recommend': true, + 'recom_type': '热门', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '7f35fa915780a55900b9d37f03996a40', + 'shares': 7, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 3071, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3229117_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '黑猫阿姨', + 'site_id': '3229117', + 'type': 'user', + 'url': 'https://tuchong.com/3229117/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3229117', + 'sites': [], + 'tags': ['极简主义,少即是多', '最美肖像照', '有温度的人像', '济南约拍圈', '人像写真摄影约拍', '咔了个图'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3229117/61478978/', + 'views': 8240 + }, + { + 'author_id': '389785', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 26, + 'content': '', + 'created_at': '', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 731, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5168, + 'img_id': 114930137, + 'img_id_str': '114930137', + 'title': '001', + 'user_id': 389785, + 'width': 3448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5168, + 'img_id': 529051556, + 'img_id_str': '529051556', + 'title': '001', + 'user_id': 389785, + 'width': 3448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 253144912, + 'img_id_str': '253144912', + 'title': '001', + 'user_id': 389785, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 23851, + 'img_id': 394965265, + 'img_id_str': '394965265', + 'title': '001', + 'user_id': 389785, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 464236295, + 'img_id_str': '464236295', + 'title': '001', + 'user_id': 389785, + 'width': 5168 + }, + { + 'description': '', + 'excerpt': '', + 'height': 10928, + 'img_id': 87208312, + 'img_id_str': '87208312', + 'title': '001', + 'user_id': 389785, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 10046, + 'img_id': 124170215, + 'img_id_str': '124170215', + 'title': '001', + 'user_id': 389785, + 'width': 3359 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5168, + 'img_id': 201437355, + 'img_id_str': '201437355', + 'title': '001', + 'user_id': 389785, + 'width': 7144 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5063, + 'img_id': 154447940, + 'img_id_str': '154447940', + 'title': '001', + 'user_id': 389785, + 'width': 3378 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '26', + 'passed_time': '2019年10月09日', + 'post_id': 55406969, + 'published_at': '2019-10-09 22:21:47', + 'recommend': true, + 'recom_type': '热门', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '7f35fa915780a55900b9d37f03996a40', + 'shares': 29, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 4329, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_389785_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '阿怪小天使', + 'site_id': '389785', + 'type': 'user', + 'url': 'https://tuchong.com/389785/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '389785', + 'sites': [], + 'tags': ['色彩', '人像'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/389785/55406969/', + 'views': 28368 + }, + { + 'author_id': '1590219', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 40, + 'content': '萤火虫DAY1 \n出镜:清水由乃\n\n拍个场照跟打仗一样...', + 'created_at': '', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '萤火虫DAY1 \n出镜:清水由乃\n\n拍个场照跟打仗一样...', + 'favorite_list_prefix': [], + 'favorites': 1034, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6181, + 'img_id': 355828066, + 'img_id_str': '355828066', + 'title': '', + 'user_id': 1590219, + 'width': 4120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 399933769, + 'img_id_str': '399933769', + 'title': '', + 'user_id': 1590219, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4160, + 'img_id': 254968217, + 'img_id_str': '254968217', + 'title': '', + 'user_id': 1590219, + 'width': 6240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 549683596, + 'img_id_str': '549683596', + 'title': '', + 'user_id': 1590219, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 380731607, + 'img_id_str': '380731607', + 'title': '', + 'user_id': 1590219, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4160, + 'img_id': 198934547, + 'img_id_str': '198934547', + 'title': '', + 'user_id': 1590219, + 'width': 7396 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5824, + 'img_id': 94601483, + 'img_id_str': '94601483', + 'title': '', + 'user_id': 1590219, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3815, + 'img_id': 370835750, + 'img_id_str': '370835750', + 'title': '', + 'user_id': 1590219, + 'width': 6783 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5824, + 'img_id': 652771493, + 'img_id_str': '652771493', + 'title': '', + 'user_id': 1590219, + 'width': 4160 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '29', + 'passed_time': '2019年07月29日', + 'post_id': 46827805, + 'published_at': '2019-07-29 22:25:47', + 'recommend': true, + 'recom_type': '热门', + 'rewardable': false, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '7f35fa915780a55900b9d37f03996a40', + 'shares': 23, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'tuopiandidai.tuchong.com', + 'followers': 10737, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1590219_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '故乡の缘风景', + 'site_id': '1590219', + 'type': 'user', + 'url': 'https://tuopiandidai.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1590219', + 'sites': [], + 'tags': ['人像', '小清新', '美女', '少女', '富士', '女孩'], + 'title': '练习', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuopiandidai.tuchong.com/46827805/', + 'views': 52188 + }, + { + 'author_id': '488929', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 35, + 'content': '阳光明媚的早晨 牛奶 面包 和你☁️', + 'created_at': '', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['日系集'], + 'excerpt': '阳光明媚的早晨 牛奶 面包 和你☁️', + 'favorite_list_prefix': [], + 'favorites': 1030, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 514754687, + 'img_id_str': '514754687', + 'title': '001', + 'user_id': 488929, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 468879705, + 'img_id_str': '468879705', + 'title': '001', + 'user_id': 488929, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 204704390, + 'img_id_str': '204704390', + 'title': '001', + 'user_id': 488929, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 528123867, + 'img_id_str': '528123867', + 'title': '001', + 'user_id': 488929, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 156731674, + 'img_id_str': '156731674', + 'title': '001', + 'user_id': 488929, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 232425720, + 'img_id_str': '232425720', + 'title': '001', + 'user_id': 488929, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 497584269, + 'img_id_str': '497584269', + 'title': '001', + 'user_id': 488929, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 442402907, + 'img_id_str': '442402907', + 'title': '001', + 'user_id': 488929, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 70158973, + 'img_id_str': '70158973', + 'title': '001', + 'user_id': 488929, + 'width': 3456 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '33', + 'passed_time': '2019年08月06日', + 'post_id': 47993130, + 'published_at': '2019-08-06 23:06:53', + 'recommend': true, + 'recom_type': '热门', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '7f35fa915780a55900b9d37f03996a40', + 'shares': 27, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 4045, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_488929_6', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '苏蓝sherry', + 'site_id': '488929', + 'type': 'user', + 'url': 'https://tuchong.com/488929/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '488929', + 'sites': [], + 'tags': ['日系集', '少女', '日系少女', '少女写真'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/488929/47993130/', + 'views': 50477 + }, + { + 'author_id': '490904', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 43, + 'content': '秋凉。', + 'created_at': '', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '分享神仙颜值', + '暖色调人像', + '驚鴻·一眸', + '约拍馆摄影作品投稿', + '糖水人像小组', + '人像爱好者', + '我们都爱日系摄影', + '河北摄影圈', + '高颜值女神聚集地', + '日系集' + ], + 'excerpt': '秋凉。', + 'favorite_list_prefix': [], + 'favorites': 1033, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 643873860, + 'img_id_str': '643873860', + 'title': '52892', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 598784501, + 'img_id_str': '598784501', + 'title': '52887', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 90487758, + 'img_id_str': '90487758', + 'title': '52886', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 413252113, + 'img_id_str': '413252113', + 'title': '52888', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 49920432, + 'img_id_str': '49920432', + 'title': '52890', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 319732118, + 'img_id_str': '319732118', + 'title': '52893', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 552254016, + 'img_id_str': '552254016', + 'title': '52889', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 294173479, + 'img_id_str': '294173479', + 'title': '52891', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 560511953, + 'img_id_str': '560511953', + 'title': '52546', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 72459318, + 'img_id_str': '72459318', + 'title': '52027', + 'user_id': 490904, + 'width': 1000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '42', + 'passed_time': '2019年11月03日', + 'post_id': 57691196, + 'published_at': '2019-11-03 16:32:20', + 'recommend': true, + 'recom_type': '热门', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '7f35fa915780a55900b9d37f03996a40', + 'shares': 34, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 12649, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_490904_8', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影师CAT', + 'site_id': '490904', + 'type': 'user', + 'url': 'https://tuchong.com/490904/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '490904', + 'sites': [], + 'tags': ['分享神仙颜值', '暖色调人像', '驚鴻·一眸', '约拍馆摄影作品投稿', '糖水人像小组', '人像爱好者'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/490904/57691196/', + 'views': 28932 + }, + { + 'author_id': '1718084', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 16, + 'content': + '出镜: \nWeibo :\n-浅域- \nhttps://weibo.com/u/5486388616\n\n摄影:@Cchua华仔\nWeibo:\n@Cchua华-仔\nhttps://weibo.com/vipcchua', + 'created_at': '', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': + '出镜: \nWeibo :\n-浅域- \nhttps://weibo.com/u/5486388616\n\n摄影:@Cchua华仔\nWeibo:\n@Cchua华-仔\nhttps://weibo.com/vipcchua', + 'favorite_list_prefix': [], + 'favorites': 762, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 219519810, + 'img_id_str': '219519810', + 'title': '', + 'user_id': 1718084, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 48142938, + 'img_id_str': '48142938', + 'title': '', + 'user_id': 1718084, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 570399487, + 'img_id_str': '570399487', + 'title': '', + 'user_id': 1718084, + 'width': 3538 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 549035080, + 'img_id_str': '549035080', + 'title': '', + 'user_id': 1718084, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 57973561, + 'img_id_str': '57973561', + 'title': '', + 'user_id': 1718084, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 128490403, + 'img_id_str': '128490403', + 'title': '', + 'user_id': 1718084, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 609327692, + 'img_id_str': '609327692', + 'title': '', + 'user_id': 1718084, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 371235416, + 'img_id_str': '371235416', + 'title': '', + 'user_id': 1718084, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8351, + 'img_id': 303078135, + 'img_id_str': '303078135', + 'title': '', + 'user_id': 1718084, + 'width': 5571 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '13', + 'passed_time': '2019年08月28日', + 'post_id': 50900424, + 'published_at': '2019-08-28 14:08:44', + 'recommend': true, + 'recom_type': '热门', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '7f35fa915780a55900b9d37f03996a40', + 'shares': 23, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': 'cchua.tuchong.com', + 'followers': 4650, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1718084_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Cchua华仔', + 'site_id': '1718084', + 'type': 'user', + 'url': 'https://cchua.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1718084', + 'sites': [], + 'tags': ['人像', 'JK'], + 'title': 'JK - Hot Summer', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://cchua.tuchong.com/50900424/', + 'views': 30474 + }, + { + 'author_id': '1413882', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 33, + 'content': 'Kitty and Beauty', + 'created_at': '', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '驚鴻·一眸', + '写真人像摄影', + '成都约拍圈子', + '成都人像爱好者', + '高颜值女神聚集地', + '日系集', + '分享神仙颜值' + ], + 'excerpt': 'Kitty and Beauty', + 'favorite_list_prefix': [], + 'favorites': 674, + 'image_count': 8, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4969, + 'img_id': 305643973, + 'img_id_str': '305643973', + 'title': '001', + 'user_id': 1413882, + 'width': 3313 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 326353053, + 'img_id_str': '326353053', + 'title': '001', + 'user_id': 1413882, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 49988311, + 'img_id_str': '49988311', + 'title': '001', + 'user_id': 1413882, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5212, + 'img_id': 129614118, + 'img_id_str': '129614118', + 'title': '001', + 'user_id': 1413882, + 'width': 3475 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3803, + 'img_id': 236896802, + 'img_id_str': '236896802', + 'title': '001', + 'user_id': 1413882, + 'width': 5705 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5788, + 'img_id': 220643714, + 'img_id_str': '220643714', + 'title': '001', + 'user_id': 1413882, + 'width': 3859 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3753, + 'img_id': 434356864, + 'img_id_str': '434356864', + 'title': '001', + 'user_id': 1413882, + 'width': 5630 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5392, + 'img_id': 512607079, + 'img_id_str': '512607079', + 'title': '001', + 'user_id': 1413882, + 'width': 3595 + } + ], + 'is_favorite': false, + 'last_read': true, + 'parent_comments': '32', + 'passed_time': '2019年12月06日', + 'post_id': 59959066, + 'published_at': '2019-12-06 09:31:29', + 'recommend': true, + 'recom_type': '热门', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '7f35fa915780a55900b9d37f03996a40', + 'shares': 29, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 2548, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1413882_3', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'Batkid', + 'site_id': '1413882', + 'type': 'user', + 'url': 'https://tuchong.com/1413882/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1413882', + 'sites': [], + 'tags': ['驚鴻·一眸', '写真人像摄影', '成都约拍圈子', '成都人像爱好者', '高颜值女神聚集地', '日系集'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1413882/59959066/', + 'views': 63307 + }, + { + 'author_id': '8564826', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 11, + 'content': '赛车娘冲鸭', + 'created_at': '2020-01-04 08:15:47', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['我要上开屏', 'Cosplay摄影集中地', '分享神仙颜值', '高颜值女神聚集地'], + 'excerpt': '赛车娘冲鸭', + 'favorite_list_prefix': [], + 'favorites': 227, + 'image_count': 7, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5982, + 'img_id': 599901817, + 'img_id_str': '599901817', + 'title': '1764398', + 'user_id': 8564826, + 'width': 3988 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6368, + 'img_id': 298108304, + 'img_id_str': '298108304', + 'title': '1764399', + 'user_id': 8564826, + 'width': 4246 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6272, + 'img_id': 178636322, + 'img_id_str': '178636322', + 'title': '1764406', + 'user_id': 8564826, + 'width': 4182 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4324, + 'img_id': 422823864, + 'img_id_str': '422823864', + 'title': '1764401', + 'user_id': 8564826, + 'width': 2882 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5258, + 'img_id': 164349529, + 'img_id_str': '164349529', + 'title': '1764402', + 'user_id': 8564826, + 'width': 3506 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4976, + 'img_id': 267044509, + 'img_id_str': '267044509', + 'title': '1764405', + 'user_id': 8564826, + 'width': 3317 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2789, + 'img_id': 611829156, + 'img_id_str': '611829156', + 'title': '1764404', + 'user_id': 8564826, + 'width': 6508 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '10', + 'passed_time': '01月04日', + 'post_id': 61377798, + 'published_at': '2020-01-04 08:15:47', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 10, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 4460, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_8564826_15', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '南廷Osimple', + 'site_id': '8564826', + 'type': 'user', + 'url': 'https://tuchong.com/8564826/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '8564826', + 'sites': [], + 'tags': ['我要上开屏', 'Cosplay摄影集中地', '分享神仙颜值', '高颜值女神聚集地'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/8564826/61377798/', + 'views': 15640 + }, + { + 'author_id': '2600543', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 23, + 'content': '出镜:@妍子坚不可摧', + 'created_at': '2019-12-25 16:12:44', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['Cosplay摄影集中地', '让摄影穿破次元壁', '高颜值女神聚集地', '图虫约拍圈子', '悦拍评片会'], + 'excerpt': '出镜:@妍子坚不可摧', + 'favorite_list_prefix': [], + 'favorites': 545, + 'image_count': 13, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 536332429, + 'img_id_str': '536332429', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 644335113, + 'img_id_str': '644335113', + 'title': '', + 'user_id': 2600543, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 581617094, + 'img_id_str': '581617094', + 'title': '', + 'user_id': 2600543, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 440256264, + 'img_id_str': '440256264', + 'title': '', + 'user_id': 2600543, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 49464774, + 'img_id_str': '49464774', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 150783459, + 'img_id_str': '150783459', + 'title': '', + 'user_id': 2600543, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 489538300, + 'img_id_str': '489538300', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 52872239, + 'img_id_str': '52872239', + 'title': '', + 'user_id': 2600543, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 230606434, + 'img_id_str': '230606434', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 642433959, + 'img_id_str': '642433959', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 369083520, + 'img_id_str': '369083520', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 482198929, + 'img_id_str': '482198929', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 482198929, + 'img_id_str': '482198929', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '21', + 'passed_time': '2019年12月25日', + 'post_id': 60962192, + 'published_at': '2019-12-25 16:12:44', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 9, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': 'sanyue015.tuchong.com', + 'followers': 13818, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2600543_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '叁月life', + 'site_id': '2600543', + 'type': 'user', + 'url': 'https://sanyue015.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2600543', + 'sites': [], + 'tags': ['Cosplay摄影集中地', '让摄影穿破次元壁', '高颜值女神聚集地', '图虫约拍圈子', '悦拍评片会', '人像'], + 'title': '#爱宕兔女郎 #写真 #约拍', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://sanyue015.tuchong.com/60962192/', + 'views': 38814 + }, + { + 'author_id': '6656530', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 86, + 'content': '', + 'created_at': '2020-01-03 15:40:44', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '摄影机位探索', + '最满意的风光照', + '每人推荐一个旅行目的地', + '图虫旅行摄影圈子', + '极致风光摄影', + '光影者的风光摄影', + '中山大学摄影圈', + '一个风光摄影圈', + '直到世界的尽头', + '清华大学摄影圈' + ], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1172, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 223003837, + 'img_id_str': '223003837', + 'title': '001', + 'user_id': 6656530, + 'width': 1250 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '74', + 'passed_time': '01月03日', + 'post_id': 61357000, + 'published_at': '2020-01-03 15:40:44', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 29, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 4861, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_6656530_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '远海视界', + 'site_id': '6656530', + 'type': 'user', + 'url': 'https://tuchong.com/6656530/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '6656530', + 'sites': [], + 'tags': [ + '摄影机位探索', + '最满意的风光照', + '每人推荐一个旅行目的地', + '图虫旅行摄影圈子', + '极致风光摄影', + '光影者的风光摄影' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/6656530/61357000/', + 'views': 21083 + }, + { + 'author_id': '2981258', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': '上海音乐学院', + 'created_at': '2020-01-16 00:17:21', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '极致风光摄影', + '我要上开屏', + '风光摄影集', + '图虫城市建筑', + '带我看看,你的城市', + '华东理工大学摄影圈', + '绝不停止记录的2019', + '我要上“首页推荐位”', + '摄影机位探索' + ], + 'excerpt': '上海音乐学院', + 'favorite_list_prefix': [], + 'favorites': 31, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4024, + 'img_id': 307284082, + 'img_id_str': '307284082', + 'title': '001', + 'user_id': 2981258, + 'width': 6048 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月16日', + 'post_id': 61744600, + 'published_at': '2020-01-16 00:17:21', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 7097, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2981258_5', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'MaxWell_Z', + 'site_id': '2981258', + 'type': 'user', + 'url': 'https://tuchong.com/2981258/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2981258', + 'sites': [], + 'tags': ['极致风光摄影', '我要上开屏', '风光摄影集', '图虫城市建筑', '带我看看,你的城市', '华东理工大学摄影圈'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2981258/61744600/', + 'views': 776 + }, + { + 'author_id': '1366156', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 43, + 'content': '', + 'created_at': '2019-12-04 19:01:28', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1188, + 'image_count': 7, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 563986825, + 'img_id_str': '563986825', + 'title': '001', + 'user_id': 1366156, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 458604479, + 'img_id_str': '458604479', + 'title': '001', + 'user_id': 1366156, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5093, + 'img_id': 385794641, + 'img_id_str': '385794641', + 'title': '001', + 'user_id': 1366156, + 'width': 3395 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 643941143, + 'img_id_str': '643941143', + 'title': '001', + 'user_id': 1366156, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 196198688, + 'img_id_str': '196198688', + 'title': '001', + 'user_id': 1366156, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 382320981, + 'img_id_str': '382320981', + 'title': '001', + 'user_id': 1366156, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 185319905, + 'img_id_str': '185319905', + 'title': '001', + 'user_id': 1366156, + 'width': 3840 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '40', + 'passed_time': '2019年12月04日', + 'post_id': 59879188, + 'published_at': '2019-12-04 19:01:28', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 48, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 1200, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1366156_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '摄影师辰子姐姐', + 'site_id': '1366156', + 'type': 'user', + 'url': 'https://tuchong.com/1366156/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1366156', + 'sites': [], + 'tags': [], + 'title': '疲倦的生活里,总有一些温柔的梦想', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1366156/59879188/', + 'views': 50196 + }, + { + 'author_id': '3557775', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 43, + 'content': '对情绪的掌控仍然生涩的像一个新手/', + 'created_at': '2019-10-08 19:14:17', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['我们都在孤独星球'], + 'excerpt': '对情绪的掌控仍然生涩的像一个新手/', + 'favorite_list_prefix': [], + 'favorites': 971, + 'image_count': 8, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2812, + 'img_id': 642559665, + 'img_id_str': '642559665', + 'title': '001', + 'user_id': 3557775, + 'width': 4680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 610119686, + 'img_id_str': '610119686', + 'title': '001', + 'user_id': 3557775, + 'width': 3179 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2847, + 'img_id': 502771888, + 'img_id_str': '502771888', + 'title': '001', + 'user_id': 3557775, + 'width': 4672 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2689, + 'img_id': 330674472, + 'img_id_str': '330674472', + 'title': '001', + 'user_id': 3557775, + 'width': 4680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 172797671, + 'img_id_str': '172797671', + 'title': '001', + 'user_id': 3557775, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3120, + 'img_id': 644395161, + 'img_id_str': '644395161', + 'title': '001', + 'user_id': 3557775, + 'width': 4680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3120, + 'img_id': 280146106, + 'img_id_str': '280146106', + 'title': '001', + 'user_id': 3557775, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2756, + 'img_id': 39366552, + 'img_id_str': '39366552', + 'title': '001', + 'user_id': 3557775, + 'width': 4680 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '33', + 'passed_time': '2019年10月08日', + 'post_id': 55260698, + 'published_at': '2019-10-08 19:14:17', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 22, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 15706, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3557775_3', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '阮儿', + 'site_id': '3557775', + 'type': 'user', + 'url': 'https://tuchong.com/3557775/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3557775', + 'sites': [], + 'tags': ['我们都在孤独星球', '在室内', '房间', '女孩', '复古人像', '优雅'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3557775/55260698/', + 'views': 45489 + }, + { + 'author_id': '6698986', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 21, + 'content': '努力会上瘾,尤其在尝到了甜头之后。', + 'created_at': '2020-01-07 16:09:18', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '私密人像写真馆', + '有温度的人像', + '好色之图', + '暖色调人像', + '高颜值女神聚集地', + '人像爱好者', + '唯美人像摄影', + '写真人像摄影', + '分享神仙颜值' + ], + 'excerpt': '努力会上瘾,尤其在尝到了甜头之后。', + 'favorite_list_prefix': [], + 'favorites': 427, + 'image_count': 5, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 368885122, + 'img_id_str': '368885122', + 'title': '2563457', + 'user_id': 6698986, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3766, + 'img_id': 334872163, + 'img_id_str': '334872163', + 'title': '2563456', + 'user_id': 6698986, + 'width': 5660 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3622, + 'img_id': 399228775, + 'img_id_str': '399228775', + 'title': '2563454', + 'user_id': 6698986, + 'width': 5355 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3686, + 'img_id': 49135390, + 'img_id_str': '49135390', + 'title': '2569205', + 'user_id': 6698986, + 'width': 5529 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3407, + 'img_id': 541376283, + 'img_id_str': '541376283', + 'title': '2569207', + 'user_id': 6698986, + 'width': 5443 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '21', + 'passed_time': '01月07日', + 'post_id': 61487820, + 'published_at': '2020-01-07 16:09:18', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '7', + 'rqt_id': '', + 'shares': 17, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 12498, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_6698986_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '柠檬的宁', + 'site_id': '6698986', + 'type': 'user', + 'url': 'https://tuchong.com/6698986/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '6698986', + 'sites': [], + 'tags': ['私密人像写真馆', '有温度的人像', '好色之图', '暖色调人像', '高颜值女神聚集地', '人像爱好者'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/6698986/61487820/', + 'views': 46774 + }, + { + 'author_id': '998297', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 18, + 'content': '摄影/后期:@霂青衣', + 'created_at': '2019-09-18 22:30:06', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['高颜值女神聚集地'], + 'excerpt': '摄影/后期:@霂青衣', + 'favorite_list_prefix': [], + 'favorites': 541, + 'image_count': 8, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 43689466, + 'img_id_str': '43689466', + 'title': '', + 'user_id': 998297, + 'width': 1066 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 262055143, + 'img_id_str': '262055143', + 'title': '', + 'user_id': 998297, + 'width': 1066 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 320578417, + 'img_id_str': '320578417', + 'title': '', + 'user_id': 998297, + 'width': 1066 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 405710123, + 'img_id_str': '405710123', + 'title': '', + 'user_id': 998297, + 'width': 1066 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 401712838, + 'img_id_str': '401712838', + 'title': '', + 'user_id': 998297, + 'width': 1066 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 218867131, + 'img_id_str': '218867131', + 'title': '', + 'user_id': 998297, + 'width': 1067 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 370255164, + 'img_id_str': '370255164', + 'title': '', + 'user_id': 998297, + 'width': 1067 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 55485282, + 'img_id_str': '55485282', + 'title': '', + 'user_id': 998297, + 'width': 1067 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '17', + 'passed_time': '2019年09月18日', + 'post_id': 52953741, + 'published_at': '2019-09-18 22:30:06', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 43, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 9709, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_998297_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '霂青衣', + 'site_id': '998297', + 'type': 'user', + 'url': 'https://tuchong.com/998297/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '998297', + 'sites': [], + 'tags': ['高颜值女神聚集地', '人像', '形象照', 'OL'], + 'title': '形象照', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/998297/52953741/', + 'views': 19278 + }, + { + 'author_id': '14392565', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 43, + 'content': '没有一朵花,从一开始就是花。\n也没有一朵花,最后以花结束。', + 'created_at': '2019-10-14 10:54:09', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['高颜值女神聚集地', '晒晒旅行打卡照', '暖色调人像', '日系集', '第三届京东摄影金像奖'], + 'excerpt': '没有一朵花,从一开始就是花。\n也没有一朵花,最后以花结束。', + 'favorite_list_prefix': [], + 'favorites': 1227, + 'image_count': 14, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4954, + 'img_id': 586003362, + 'img_id_str': '586003362', + 'title': '', + 'user_id': 14392565, + 'width': 3303 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3648, + 'img_id': 109228765, + 'img_id_str': '109228765', + 'title': '', + 'user_id': 14392565, + 'width': 5472 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 142062116, + 'img_id_str': '142062116', + 'title': '', + 'user_id': 14392565, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2981, + 'img_id': 202813899, + 'img_id_str': '202813899', + 'title': '', + 'user_id': 14392565, + 'width': 5472 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3648, + 'img_id': 585872667, + 'img_id_str': '585872667', + 'title': '', + 'user_id': 14392565, + 'width': 5472 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 85701830, + 'img_id_str': '85701830', + 'title': '', + 'user_id': 14392565, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 263893259, + 'img_id_str': '263893259', + 'title': '', + 'user_id': 14392565, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5238, + 'img_id': 333951770, + 'img_id_str': '333951770', + 'title': '', + 'user_id': 14392565, + 'width': 3492 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5389, + 'img_id': 620605720, + 'img_id_str': '620605720', + 'title': '', + 'user_id': 14392565, + 'width': 3593 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4889, + 'img_id': 438940508, + 'img_id_str': '438940508', + 'title': '', + 'user_id': 14392565, + 'width': 3259 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 537506508, + 'img_id_str': '537506508', + 'title': '', + 'user_id': 14392565, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4916, + 'img_id': 217756215, + 'img_id_str': '217756215', + 'title': '', + 'user_id': 14392565, + 'width': 3277 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5351, + 'img_id': 351711606, + 'img_id_str': '351711606', + 'title': '', + 'user_id': 14392565, + 'width': 3568 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5260, + 'img_id': 332641393, + 'img_id_str': '332641393', + 'title': '', + 'user_id': 14392565, + 'width': 3506 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '39', + 'passed_time': '2019年10月14日', + 'post_id': 55860620, + 'published_at': '2019-10-14 10:54:09', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 50, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 3268, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_14392565_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '是小汤圆喔', + 'site_id': '14392565', + 'type': 'user', + 'url': 'https://tuchong.com/14392565/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '14392565', + 'sites': [], + 'tags': ['高颜值女神聚集地', '晒晒旅行打卡照', '暖色调人像', '日系集', '第三届京东摄影金像奖', '人像'], + 'title': '「 解语 · 花 」', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/14392565/55860620/', + 'views': 37245 + }, + { + 'author_id': '5351023', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 45, + 'content': + '初冻的Minnewanka Lake — Banff 的Minnewanka 湖总是比其他的湖冻得晚些。初冻的湖边有很多的冰花和薄薄的冰层,湖水随风飘荡,冲击着时瘾时现的薄冰,晚霞后的蓝沁人心扉,让这寂静的湖畔充满了梦幻般的气息……', + 'created_at': '2019-12-28 11:16:52', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '天津摄影论坛', + '我们都爱黑白摄影', + '成都爬楼集团圈子', + '中国星空摄影圈子', + '星空小分队圈子', + '杭州风光圈子', + '清华大学摄影圈', + '重庆印象圈', + '神奇的慢门世界', + '光环摄影美学' + ], + 'excerpt': + '初冻的Minnewanka Lake — Banff 的Minnewanka 湖总是比其他的湖冻得晚些。初冻的湖边有很多的冰花和薄薄的冰层,湖水随风飘荡,冲击着时瘾时现的薄冰,晚霞后的蓝沁人心扉,让这寂静的湖畔充满了梦幻般的气息……', + 'favorite_list_prefix': [], + 'favorites': 184, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 853, + 'img_id': 48022540, + 'img_id_str': '48022540', + 'title': '001', + 'user_id': 5351023, + 'width': 1280 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '23', + 'passed_time': '2019年12月28日', + 'post_id': 61084424, + 'published_at': '2019-12-28 11:16:52', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 5, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 1015, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_5351023_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '落基山逐光者', + 'site_id': '5351023', + 'type': 'user', + 'url': 'https://tuchong.com/5351023/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '5351023', + 'sites': [], + 'tags': [ + '天津摄影论坛', + '我们都爱黑白摄影', + '成都爬楼集团圈子', + '中国星空摄影圈子', + '星空小分队圈子', + '杭州风光圈子' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/5351023/61084424/', + 'views': 2931 + }, + { + 'author_id': '400799', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '', + 'created_at': '2020-01-06 18:13:16', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 39, + 'image_count': 82, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3776, + 'img_id': 397199144, + 'img_id_str': '397199144', + 'title': '', + 'user_id': 400799, + 'width': 3021 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3492, + 'img_id': 458278443, + 'img_id_str': '458278443', + 'title': '', + 'user_id': 400799, + 'width': 2794 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3645, + 'img_id': 441174212, + 'img_id_str': '441174212', + 'title': '', + 'user_id': 400799, + 'width': 2603 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3588, + 'img_id': 384485277, + 'img_id_str': '384485277', + 'title': '', + 'user_id': 400799, + 'width': 2870 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2419, + 'img_id': 476693913, + 'img_id_str': '476693913', + 'title': '', + 'user_id': 400799, + 'width': 1935 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1798, + 'img_id': 444254015, + 'img_id_str': '444254015', + 'title': '', + 'user_id': 400799, + 'width': 2517 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2483, + 'img_id': 309708369, + 'img_id_str': '309708369', + 'title': '', + 'user_id': 400799, + 'width': 1774 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2400, + 'img_id': 587318925, + 'img_id_str': '587318925', + 'title': '', + 'user_id': 400799, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2454, + 'img_id': 338086060, + 'img_id_str': '338086060', + 'title': '', + 'user_id': 400799, + 'width': 1753 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2526, + 'img_id': 291948293, + 'img_id_str': '291948293', + 'title': '', + 'user_id': 400799, + 'width': 1804 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2656, + 'img_id': 592364900, + 'img_id_str': '592364900', + 'title': '', + 'user_id': 400799, + 'width': 1897 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2367, + 'img_id': 443205086, + 'img_id_str': '443205086', + 'title': '', + 'user_id': 400799, + 'width': 1894 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2362, + 'img_id': 418826226, + 'img_id_str': '418826226', + 'title': '', + 'user_id': 400799, + 'width': 1687 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2500, + 'img_id': 464963283, + 'img_id_str': '464963283', + 'title': '', + 'user_id': 400799, + 'width': 1786 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2030, + 'img_id': 547342041, + 'img_id_str': '547342041', + 'title': '', + 'user_id': 400799, + 'width': 1624 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2691, + 'img_id': 150717796, + 'img_id_str': '150717796', + 'title': '', + 'user_id': 400799, + 'width': 1922 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2450, + 'img_id': 607110833, + 'img_id_str': '607110833', + 'title': '', + 'user_id': 400799, + 'width': 1749 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3581, + 'img_id': 512476461, + 'img_id_str': '512476461', + 'title': '', + 'user_id': 400799, + 'width': 2865 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3673, + 'img_id': 488359038, + 'img_id_str': '488359038', + 'title': '', + 'user_id': 400799, + 'width': 2938 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2665, + 'img_id': 207996420, + 'img_id_str': '207996420', + 'title': '', + 'user_id': 400799, + 'width': 1904 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2697, + 'img_id': 403425583, + 'img_id_str': '403425583', + 'title': '', + 'user_id': 400799, + 'width': 1926 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2674, + 'img_id': 221038384, + 'img_id_str': '221038384', + 'title': '', + 'user_id': 400799, + 'width': 1909 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2722, + 'img_id': 297649340, + 'img_id_str': '297649340', + 'title': '', + 'user_id': 400799, + 'width': 1944 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2682, + 'img_id': 236504635, + 'img_id_str': '236504635', + 'title': '', + 'user_id': 400799, + 'width': 1915 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2683, + 'img_id': 610453101, + 'img_id_str': '610453101', + 'title': '', + 'user_id': 400799, + 'width': 1917 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2691, + 'img_id': 250856860, + 'img_id_str': '250856860', + 'title': '', + 'user_id': 400799, + 'width': 1922 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2681, + 'img_id': 112051243, + 'img_id_str': '112051243', + 'title': '', + 'user_id': 400799, + 'width': 1915 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1820, + 'img_id': 626312748, + 'img_id_str': '626312748', + 'title': '', + 'user_id': 400799, + 'width': 2548 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2701, + 'img_id': 172803161, + 'img_id_str': '172803161', + 'title': '', + 'user_id': 400799, + 'width': 1929 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2621, + 'img_id': 292800531, + 'img_id_str': '292800531', + 'title': '', + 'user_id': 400799, + 'width': 1872 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2657, + 'img_id': 612681155, + 'img_id_str': '612681155', + 'title': '', + 'user_id': 400799, + 'width': 1898 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1810, + 'img_id': 647349529, + 'img_id_str': '647349529', + 'title': '', + 'user_id': 400799, + 'width': 2534 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2596, + 'img_id': 400410407, + 'img_id_str': '400410407', + 'title': '', + 'user_id': 400799, + 'width': 1854 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2651, + 'img_id': 389335136, + 'img_id_str': '389335136', + 'title': '', + 'user_id': 400799, + 'width': 1894 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2716, + 'img_id': 227788200, + 'img_id_str': '227788200', + 'title': '', + 'user_id': 400799, + 'width': 1940 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1816, + 'img_id': 509855541, + 'img_id_str': '509855541', + 'title': '', + 'user_id': 400799, + 'width': 2542 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3788, + 'img_id': 633849652, + 'img_id_str': '633849652', + 'title': '', + 'user_id': 400799, + 'width': 3030 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1759, + 'img_id': 483378938, + 'img_id_str': '483378938', + 'title': '', + 'user_id': 400799, + 'width': 2462 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2701, + 'img_id': 197379815, + 'img_id_str': '197379815', + 'title': '', + 'user_id': 400799, + 'width': 1929 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2421, + 'img_id': 184338254, + 'img_id_str': '184338254', + 'title': '', + 'user_id': 400799, + 'width': 1937 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2701, + 'img_id': 455001108, + 'img_id_str': '455001108', + 'title': '', + 'user_id': 400799, + 'width': 1929 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1786, + 'img_id': 323471379, + 'img_id_str': '323471379', + 'title': '', + 'user_id': 400799, + 'width': 2500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2446, + 'img_id': 385664587, + 'img_id_str': '385664587', + 'title': '', + 'user_id': 400799, + 'width': 1747 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2630, + 'img_id': 644007516, + 'img_id_str': '644007516', + 'title': '', + 'user_id': 400799, + 'width': 1879 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2656, + 'img_id': 337299116, + 'img_id_str': '337299116', + 'title': '', + 'user_id': 400799, + 'width': 1897 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2750, + 'img_id': 135121161, + 'img_id_str': '135121161', + 'title': '', + 'user_id': 400799, + 'width': 1964 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2484, + 'img_id': 124241118, + 'img_id_str': '124241118', + 'title': '', + 'user_id': 400799, + 'width': 1774 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1841, + 'img_id': 210683644, + 'img_id_str': '210683644', + 'title': '', + 'user_id': 400799, + 'width': 2301 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2683, + 'img_id': 341165926, + 'img_id_str': '341165926', + 'title': '', + 'user_id': 400799, + 'width': 1915 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2609, + 'img_id': 111527452, + 'img_id_str': '111527452', + 'title': '', + 'user_id': 400799, + 'width': 1863 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2316, + 'img_id': 352241235, + 'img_id_str': '352241235', + 'title': '', + 'user_id': 400799, + 'width': 1853 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2739, + 'img_id': 551339662, + 'img_id_str': '551339662', + 'title': '', + 'user_id': 400799, + 'width': 1956 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2568, + 'img_id': 354404030, + 'img_id_str': '354404030', + 'title': '', + 'user_id': 400799, + 'width': 1834 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2422, + 'img_id': 636602071, + 'img_id_str': '636602071', + 'title': '', + 'user_id': 400799, + 'width': 1938 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1795, + 'img_id': 558811077, + 'img_id_str': '558811077', + 'title': '', + 'user_id': 400799, + 'width': 2513 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3591, + 'img_id': 130139852, + 'img_id_str': '130139852', + 'title': '', + 'user_id': 400799, + 'width': 2873 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3474, + 'img_id': 40224230, + 'img_id_str': '40224230', + 'title': '', + 'user_id': 400799, + 'width': 2779 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3591, + 'img_id': 242009839, + 'img_id_str': '242009839', + 'title': '', + 'user_id': 400799, + 'width': 2873 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2710, + 'img_id': 470992412, + 'img_id_str': '470992412', + 'title': '', + 'user_id': 400799, + 'width': 1936 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2722, + 'img_id': 62638022, + 'img_id_str': '62638022', + 'title': '', + 'user_id': 400799, + 'width': 1945 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1870, + 'img_id': 191939971, + 'img_id_str': '191939971', + 'title': '', + 'user_id': 400799, + 'width': 2618 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2685, + 'img_id': 538035809, + 'img_id_str': '538035809', + 'title': '', + 'user_id': 400799, + 'width': 1918 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2627, + 'img_id': 628344420, + 'img_id_str': '628344420', + 'title': '', + 'user_id': 400799, + 'width': 1876 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2664, + 'img_id': 56215276, + 'img_id_str': '56215276', + 'title': '', + 'user_id': 400799, + 'width': 1903 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2637, + 'img_id': 538232464, + 'img_id_str': '538232464', + 'title': '', + 'user_id': 400799, + 'width': 1884 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2651, + 'img_id': 395167512, + 'img_id_str': '395167512', + 'title': '', + 'user_id': 400799, + 'width': 1894 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2498, + 'img_id': 481478082, + 'img_id_str': '481478082', + 'title': '', + 'user_id': 400799, + 'width': 1784 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2653, + 'img_id': 628802784, + 'img_id_str': '628802784', + 'title': '', + 'user_id': 400799, + 'width': 1895 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2614, + 'img_id': 401393601, + 'img_id_str': '401393601', + 'title': '', + 'user_id': 400799, + 'width': 1867 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2687, + 'img_id': 74761691, + 'img_id_str': '74761691', + 'title': '', + 'user_id': 400799, + 'width': 1919 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2560, + 'img_id': 451003804, + 'img_id_str': '451003804', + 'title': '', + 'user_id': 400799, + 'width': 1829 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1781, + 'img_id': 424724213, + 'img_id_str': '424724213', + 'title': '', + 'user_id': 400799, + 'width': 2493 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2554, + 'img_id': 525059563, + 'img_id_str': '525059563', + 'title': '', + 'user_id': 400799, + 'width': 1825 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2650, + 'img_id': 644728507, + 'img_id_str': '644728507', + 'title': '', + 'user_id': 400799, + 'width': 1893 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2666, + 'img_id': 312002246, + 'img_id_str': '312002246', + 'title': '', + 'user_id': 400799, + 'width': 1904 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2649, + 'img_id': 129811994, + 'img_id_str': '129811994', + 'title': '', + 'user_id': 400799, + 'width': 1892 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2656, + 'img_id': 468567689, + 'img_id_str': '468567689', + 'title': '', + 'user_id': 400799, + 'width': 1897 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1923, + 'img_id': 605406847, + 'img_id_str': '605406847', + 'title': '', + 'user_id': 400799, + 'width': 2693 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2738, + 'img_id': 396150023, + 'img_id_str': '396150023', + 'title': '', + 'user_id': 400799, + 'width': 1956 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2745, + 'img_id': 341559197, + 'img_id_str': '341559197', + 'title': '', + 'user_id': 400799, + 'width': 1960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2643, + 'img_id': 95536630, + 'img_id_str': '95536630', + 'title': '', + 'user_id': 400799, + 'width': 1888 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2694, + 'img_id': 128173382, + 'img_id_str': '128173382', + 'title': '', + 'user_id': 400799, + 'width': 1924 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '01月06日', + 'post_id': 61458416, + 'published_at': '2020-01-06 18:13:16', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'baiyizhang.tuchong.com', + 'followers': 4888, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_400799_10', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '白一张', + 'site_id': '400799', + 'type': 'user', + 'url': 'https://baiyizhang.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '400799', + 'sites': [], + 'tags': ['人像', '色彩', '风光', '美女', '生活', '日系'], + 'title': 'Fall/Winter', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://baiyizhang.tuchong.com/61458416/', + 'views': 1816 + }, + { + 'author_id': '7771840', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '行走在城市中的恋人 记录瞬间', + 'created_at': '2019-12-29 21:38:25', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '行走在城市中的恋人 记录瞬间', + 'favorite_list_prefix': [], + 'favorites': 18, + 'image_count': 14, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1080, + 'img_id': 602981353, + 'img_id_str': '602981353', + 'title': '1479416', + 'user_id': 7771840, + 'width': 1701 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1080, + 'img_id': 345294272, + 'img_id_str': '345294272', + 'title': '1479417', + 'user_id': 7771840, + 'width': 1875 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 325174269, + 'img_id_str': '325174269', + 'title': '1479418', + 'user_id': 7771840, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1080, + 'img_id': 512542767, + 'img_id_str': '512542767', + 'title': '1479419', + 'user_id': 7771840, + 'width': 1620 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 362923164, + 'img_id_str': '362923164', + 'title': '1479420', + 'user_id': 7771840, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 95667579, + 'img_id_str': '95667579', + 'title': '1479421', + 'user_id': 7771840, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1674, + 'img_id': 63947943, + 'img_id_str': '63947943', + 'title': '1479422', + 'user_id': 7771840, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1080, + 'img_id': 326944005, + 'img_id_str': '326944005', + 'title': '1479423', + 'user_id': 7771840, + 'width': 1620 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1685, + 'img_id': 197248739, + 'img_id_str': '197248739', + 'title': '1479424', + 'user_id': 7771840, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 118998784, + 'img_id_str': '118998784', + 'title': '1479425', + 'user_id': 7771840, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1743, + 'img_id': 497403766, + 'img_id_str': '497403766', + 'title': '1479426', + 'user_id': 7771840, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 98682236, + 'img_id_str': '98682236', + 'title': '1479427', + 'user_id': 7771840, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 62244686, + 'img_id_str': '62244686', + 'title': '1479428', + 'user_id': 7771840, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 357091107, + 'img_id_str': '357091107', + 'title': '1479429', + 'user_id': 7771840, + 'width': 1080 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '2019年12月29日', + 'post_id': 61157165, + 'published_at': '2019-12-29 21:38:25', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深婚礼婚纱摄影师', + 'domain': '', + 'followers': 1361, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_7771840_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影师高登俊', + 'site_id': '7771840', + 'type': 'user', + 'url': 'https://tuchong.com/7771840/', + 'verification_list': [ + {'verification_reason': '资深婚礼婚纱摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '7771840', + 'sites': [], + 'tags': ['年轻', '旅行', '城市', '街拍'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/7771840/61157165/', + 'views': 906 + }, + { + 'author_id': '3939189', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '', + 'created_at': '2019-12-30 17:11:15', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['1SharePlus静物', '1252PHOTO', '我要上开屏', '拍拍好吃的', '图库热卖图:春'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 50, + 'image_count': 4, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2049, + 'img_id': 507233688, + 'img_id_str': '507233688', + 'title': '001', + 'user_id': 3939189, + 'width': 1366 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2048, + 'img_id': 471319780, + 'img_id_str': '471319780', + 'title': '001', + 'user_id': 3939189, + 'width': 1365 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1917, + 'img_id': 533185799, + 'img_id_str': '533185799', + 'title': '001', + 'user_id': 3939189, + 'width': 1278 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2048, + 'img_id': 448841033, + 'img_id_str': '448841033', + 'title': '001', + 'user_id': 3939189, + 'width': 1366 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '2019年12月30日', + 'post_id': 61188859, + 'published_at': '2019-12-30 17:11:15', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深美食摄影师', + 'domain': '', + 'followers': 5554, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3939189_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Marzo27', + 'site_id': '3939189', + 'type': 'user', + 'url': 'https://tuchong.com/3939189/', + 'verification_list': [ + {'verification_reason': '资深美食摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3939189', + 'sites': [], + 'tags': ['1SharePlus静物', '1252PHOTO', '我要上开屏', '拍拍好吃的', '图库热卖图:春', '面条'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3939189/61188859/', + 'views': 1814 + }, + { + 'author_id': '3261912', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 88, + 'content': '赛博朋克重庆 鸿恩阁 夜景 雾都', + 'created_at': '2019-11-10 13:52:05', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['图虫城市建筑'], + 'excerpt': '赛博朋克重庆 鸿恩阁 夜景 雾都', + 'favorite_list_prefix': [], + 'favorites': 1331, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5833, + 'img_id': 67812640, + 'img_id_str': '67812640', + 'title': '2007852', + 'user_id': 3261912, + 'width': 3889 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3889, + 'img_id': 256294309, + 'img_id_str': '256294309', + 'title': '2007846', + 'user_id': 3261912, + 'width': 5833 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '66', + 'passed_time': '2019年11月10日', + 'post_id': 58244993, + 'published_at': '2019-11-10 13:52:05', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 61, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 1385, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3261912_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '川包Vision', + 'site_id': '3261912', + 'type': 'user', + 'url': 'https://tuchong.com/3261912/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3261912', + 'sites': [], + 'tags': ['图虫城市建筑', '宝塔', '寺庙', '重庆', '夜晚'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3261912/58244993/', + 'views': 43823 + }, + { + 'author_id': '3994129', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 11, + 'content': 'Next Space', + 'created_at': '2020-01-14 09:04:19', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '摄影机位探索', + '我在西安拍照', + '极简主义摄影', + '西安摄影爱好者', + '陕西科技大学摄影圈', + '一起拍建筑', + '我们都是城市探险家', + '西安爬楼联盟圈子' + ], + 'excerpt': 'Next Space', + 'favorite_list_prefix': [], + 'favorites': 80, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 200525350, + 'img_id_str': '200525350', + 'title': '924235', + 'user_id': 3994129, + 'width': 6000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '8', + 'passed_time': '01月14日', + 'post_id': 61692488, + 'published_at': '2020-01-14 09:04:19', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 3, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 1587, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3994129_12', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'DonyPhotography', + 'site_id': '3994129', + 'type': 'user', + 'url': 'https://tuchong.com/3994129/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3994129', + 'sites': [], + 'tags': ['摄影机位探索', '我在西安拍照', '极简主义摄影', '西安摄影爱好者', '陕西科技大学摄影圈', '一起拍建筑'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3994129/61692488/', + 'views': 1735 + }, + { + 'author_id': '451770', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 42, + 'content': + '如果一定要给2019做个总结的话,那么我的就是:\n@帅嘤嘤 和@ 保时捷\n\n2020年我们也要离开保时捷,离开这个熟悉的创作环境,离开那么多支持帮助我们的家人、朋友和同事了。\n\n我生活的那个小城市有太多值得留恋的地方,但我一直追求的不是安稳的生活,而是创作给我带来的那几分钟惊喜。2019下半年我基本没有拍创作,并非没有灵感,而是因为从构思到时候我就知道作品最终是啥样的,是什么水准的,会获得什么反响,所以想一想就行了不用拍了。不能带来惊喜的片子,都显得毫无意义。所以只能选择离开。\n\n很幸运的是,我靠着一堆猫片申请到了澳洲的摄影硕士,导师面试的时候说“我看你的片子都忍不住笑出声”。估计他也没见过拿猫来申请专业的人吧。\n\n后来家里人和同事都觉得我脑子秀逗了,读了两个研还要继续读第三个。不过我觉得,能跟着一流的艺术家磨练意识、探讨创作才是最有价值的事情。文凭归根结底只是一张纸而已。\n\n感谢帅帅愿意陪我再去南半球的岛上冒险,她是个很宅的人,怕虫,喜欢去的地方是日本,恋爱到结婚这么久,一直陪我住在一个四十平的出租屋,接下来还得陪我去土澳住更小的房间,跟袋鼠搏斗,跟苍蝇抢食,出门还得担心毒蜘蛛毒蝙蝠毒蜥蜴的袭击。接下来的两年,或许对她来说会更不容易吧。\n\n也感谢大家对我这个照相师傅的支持,明年我的作品和视频也会努力给大家带来一些惊喜的。', + 'created_at': '2020-01-01 15:02:04', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['对短发姑娘没有任何抵抗力!'], + 'excerpt': + '如果一定要给2019做个总结的话,那么我的就是:\n@帅嘤嘤 和@ 保时捷\n\n2020年我们也要离开保时捷,离开这个熟悉的创作环境,离开那么多支持帮助我们的家人、朋友和同事了。\n\n我生活的那个小城市有太多值得留恋的地方,但我一直追求的不是安稳的生活,而是创作给我带来的那几分钟惊喜。2019下半年我基本没有拍创作,并非没有灵感,而是因为从构思到时候我就知道作品最终是啥样的,是什么水准的,会获得什么反响,所以想一想就行了...', + 'favorite_list_prefix': [], + 'favorites': 885, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 900, + 'img_id': 235193859, + 'img_id_str': '235193859', + 'title': '001', + 'user_id': 451770, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1400, + 'img_id': 124765496, + 'img_id_str': '124765496', + 'title': '001', + 'user_id': 451770, + 'width': 1400 + }, + { + 'description': '', + 'excerpt': '', + 'height': 788, + 'img_id': 500313678, + 'img_id_str': '500313678', + 'title': '001', + 'user_id': 451770, + 'width': 1400 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1400, + 'img_id': 212888318, + 'img_id_str': '212888318', + 'title': '001', + 'user_id': 451770, + 'width': 1400 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1400, + 'img_id': 159172011, + 'img_id_str': '159172011', + 'title': '001', + 'user_id': 451770, + 'width': 1400 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1400, + 'img_id': 407185675, + 'img_id_str': '407185675', + 'title': '001', + 'user_id': 451770, + 'width': 1400 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1067, + 'img_id': 85247203, + 'img_id_str': '85247203', + 'title': '001', + 'user_id': 451770, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1280, + 'img_id': 354993273, + 'img_id_str': '354993273', + 'title': '001', + 'user_id': 451770, + 'width': 854 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1400, + 'img_id': 152280773, + 'img_id_str': '152280773', + 'title': '001', + 'user_id': 451770, + 'width': 1400 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '42', + 'passed_time': '01月01日', + 'post_id': 61282535, + 'published_at': '2020-01-01 15:02:04', + 'recommend': true, + 'recom_type': '', + 'rewardable': false, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 52, + 'site': { + 'description': '资深创意摄影师', + 'domain': '', + 'followers': 14243, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_451770_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Terry_F', + 'site_id': '451770', + 'type': 'user', + 'url': 'https://tuchong.com/451770/', + 'verification_list': [ + {'verification_reason': '资深创意摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '451770', + 'sites': [], + 'tags': ['对短发姑娘没有任何抵抗力!', '人像', '总结', '猫', '女神'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/451770/61282535/', + 'views': 44813 + }, + { + 'author_id': '958201', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 3, + 'content': null, + 'created_at': '2019-12-24 09:59:11', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': + '拍瀑布的要点摘录:1.瀑布构图,一般用竖构图,但我更建议您现场横竖都比对一下;2.竖构图的时候,快装板下方的螺丝,一定要拧死,以防慢门长曝过程中,快装板松动,导致照片模糊;3.如果构图下方,是瀑布下的水面,那一定要有暗调的岩石、植物,来平衡构图重量;4.构图留有瀑布下方的水面,要用偏振镜滤除其多余反光;5.要想把瀑布拍出那种如丝般的感觉,快门速度一定要慢,用慢速快门记录水流或者光线的轨迹。把模式转盘到TV...', + 'favorite_list_prefix': [], + 'favorites': 16, + 'image_count': 0, + 'images': [], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '2019年12月24日', + 'post_id': 60905660, + 'published_at': '2019-12-24 09:59:11', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': 'Kase卡色滤镜官方图虫号', + 'domain': '', + 'followers': 1671, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_958201_14', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Kase卡色', + 'site_id': '958201', + 'type': 'user', + 'url': 'https://tuchong.com/958201/', + 'verification_list': [ + {'verification_reason': 'Kase卡色滤镜官方图虫号', 'verification_type': 11} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '958201', + 'sites': [], + 'tags': ['风光', '城市', '滤镜', '旅行'], + 'title': '【教程】手把手教您拍好瀑布流水', + 'title_image': { + 'width': 600, + 'height': 400, + 'url': 'https://tuchong.pstatp.com/958201/l/301319452.jpg' + }, + 'type': 'text', + 'update': false, + 'url': 'https://tuchong.com/958201/t/60905660/', + 'views': 984 + }, + { + 'author_id': '111634', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 22, + 'content': '天灯节', + 'created_at': '2019-12-18 17:54:23', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '北京人像摄影', + '「行摄间」2组', + '魔都扫街', + '行摄部落', + '胶片人文和纪实交流', + '驚鴻·一眸', + '人文纪实手册', + '世界至色,缤纷无常', + '街头拍客', + '生于街头' + ], + 'excerpt': '天灯节', + 'favorite_list_prefix': [], + 'favorites': 171, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1648, + 'img_id': 410240105, + 'img_id_str': '410240105', + 'title': '298888', + 'user_id': 111634, + 'width': 2400 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '21', + 'passed_time': '2019年12月18日', + 'post_id': 60652765, + 'published_at': '2019-12-18 17:54:23', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 5, + 'site': { + 'description': '资深纪实摄影师', + 'domain': '', + 'followers': 18167, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_111634_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '雾里独自徘徊', + 'site_id': '111634', + 'type': 'user', + 'url': 'https://tuchong.com/111634/', + 'verification_list': [ + {'verification_reason': '资深纪实摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '111634', + 'sites': [], + 'tags': ['北京人像摄影', '「行摄间」2组', '魔都扫街', '行摄部落', '胶片人文和纪实交流', '驚鴻·一眸'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/111634/60652765/', + 'views': 3102 + }, + { + 'author_id': '6698986', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 9, + 'content': '清晨', + 'created_at': '2020-01-07 19:28:08', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '私密人像写真馆', + '有温度的人像', + '好色之图', + '暖色调人像', + '高颜值女神聚集地', + '写真人像摄影', + '分享神仙颜值', + '取景器里的小美好' + ], + 'excerpt': '清晨', + 'favorite_list_prefix': [], + 'favorites': 235, + 'image_count': 5, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 448316957, + 'img_id_str': '448316957', + 'title': '2743986', + 'user_id': 6698986, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 214550353, + 'img_id_str': '214550353', + 'title': '2743987', + 'user_id': 6698986, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3258, + 'img_id': 421447242, + 'img_id_str': '421447242', + 'title': '2743990', + 'user_id': 6698986, + 'width': 5093 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 506775070, + 'img_id_str': '506775070', + 'title': '2743989', + 'user_id': 6698986, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 347325925, + 'img_id_str': '347325925', + 'title': '2743988', + 'user_id': 6698986, + 'width': 5760 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '9', + 'passed_time': '01月07日', + 'post_id': 61493152, + 'published_at': '2020-01-07 19:28:08', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '3', + 'rqt_id': '', + 'shares': 2, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 12498, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_6698986_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '柠檬的宁', + 'site_id': '6698986', + 'type': 'user', + 'url': 'https://tuchong.com/6698986/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '6698986', + 'sites': [], + 'tags': ['私密人像写真馆', '有温度的人像', '好色之图', '暖色调人像', '高颜值女神聚集地', '写真人像摄影'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/6698986/61493152/', + 'views': 20191 + }, + { + 'author_id': '6698986', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 16, + 'content': '欢迎大家加入圈子: 私密人像写真馆', + 'created_at': '2020-01-07 15:52:00', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '私密人像写真馆', + '有温度的人像', + '好色之图', + '糖水人像小组', + '暖色调人像', + '高颜值女神聚集地', + '人像爱好者', + '唯美人像摄影', + '写真人像摄影', + '分享神仙颜值' + ], + 'excerpt': '欢迎大家加入圈子: 私密人像写真馆', + 'favorite_list_prefix': [], + 'favorites': 410, + 'image_count': 5, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 606979521, + 'img_id_str': '606979521', + 'title': '2730672', + 'user_id': 6698986, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3576, + 'img_id': 403293789, + 'img_id_str': '403293789', + 'title': '2730673', + 'user_id': 6698986, + 'width': 5214 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4217, + 'img_id': 654296348, + 'img_id_str': '654296348', + 'title': '2730671', + 'user_id': 6698986, + 'width': 2812 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5380, + 'img_id': 296207613, + 'img_id_str': '296207613', + 'title': '2730669', + 'user_id': 6698986, + 'width': 3586 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3191, + 'img_id': 479249658, + 'img_id_str': '479249658', + 'title': '2730668', + 'user_id': 6698986, + 'width': 4785 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '16', + 'passed_time': '01月07日', + 'post_id': 61487401, + 'published_at': '2020-01-07 15:52:00', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '8', + 'rqt_id': '', + 'shares': 12, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 12498, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_6698986_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '柠檬的宁', + 'site_id': '6698986', + 'type': 'user', + 'url': 'https://tuchong.com/6698986/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '6698986', + 'sites': [], + 'tags': ['私密人像写真馆', '有温度的人像', '好色之图', '糖水人像小组', '暖色调人像', '高颜值女神聚集地'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/6698986/61487401/', + 'views': 33570 + }, + { + 'author_id': '1333118', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 33, + 'content': '', + 'created_at': '2019-10-10 18:06:26', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['图虫封面你做主', '胶片集'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 545, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2706, + 'img_id': 36548714, + 'img_id_str': '36548714', + 'title': '001', + 'user_id': 1333118, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3538, + 'img_id': 638037960, + 'img_id_str': '638037960', + 'title': '001', + 'user_id': 1333118, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5010, + 'img_id': 85504887, + 'img_id_str': '85504887', + 'title': '001', + 'user_id': 1333118, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5412, + 'img_id': 654750010, + 'img_id_str': '654750010', + 'title': '001', + 'user_id': 1333118, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5010, + 'img_id': 397586770, + 'img_id_str': '397586770', + 'title': '001', + 'user_id': 1333118, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5236, + 'img_id': 44413374, + 'img_id_str': '44413374', + 'title': '001', + 'user_id': 1333118, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6097, + 'img_id': 66236668, + 'img_id_str': '66236668', + 'title': '001', + 'user_id': 1333118, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2695, + 'img_id': 301248759, + 'img_id_str': '301248759', + 'title': '001', + 'user_id': 1333118, + 'width': 1925 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1731, + 'img_id': 468365722, + 'img_id_str': '468365722', + 'title': '001', + 'user_id': 1333118, + 'width': 2423 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '33', + 'passed_time': '2019年10月10日', + 'post_id': 55482460, + 'published_at': '2019-10-10 18:06:26', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 35, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'kk-lee.tuchong.com', + 'followers': 4528, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1333118_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'KK-LEE', + 'site_id': '1333118', + 'type': 'user', + 'url': 'https://kk-lee.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1333118', + 'sites': [], + 'tags': ['图虫封面你做主', '胶片集', '女孩', '人像'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://kk-lee.tuchong.com/55482460/', + 'views': 58251 + }, + { + 'author_id': '484724', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 73, + 'content': '这一刻它们是自由的,我也是。', + 'created_at': '2019-09-26 10:25:46', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['第三届京东摄影金像奖'], + 'excerpt': '这一刻它们是自由的,我也是。', + 'favorite_list_prefix': [], + 'favorites': 1036, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2736, + 'img_id': 555067306, + 'img_id_str': '555067306', + 'title': '001', + 'user_id': 484724, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2736, + 'img_id': 459909236, + 'img_id_str': '459909236', + 'title': '001', + 'user_id': 484724, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2736, + 'img_id': 538159198, + 'img_id_str': '538159198', + 'title': '001', + 'user_id': 484724, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2306, + 'img_id': 467117931, + 'img_id_str': '467117931', + 'title': '001', + 'user_id': 484724, + 'width': 3072 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2736, + 'img_id': 314878430, + 'img_id_str': '314878430', + 'title': '001', + 'user_id': 484724, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2736, + 'img_id': 169191388, + 'img_id_str': '169191388', + 'title': '001', + 'user_id': 484724, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2736, + 'img_id': 479570082, + 'img_id_str': '479570082', + 'title': '001', + 'user_id': 484724, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2976, + 'img_id': 541370432, + 'img_id_str': '541370432', + 'title': '001', + 'user_id': 484724, + 'width': 3968 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2331, + 'img_id': 506511669, + 'img_id_str': '506511669', + 'title': '001', + 'user_id': 484724, + 'width': 3108 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '72', + 'passed_time': '2019年09月26日', + 'post_id': 53599767, + 'published_at': '2019-09-26 10:25:46', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 54, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 4625, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_484724_32', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'ClouinKim', + 'site_id': '484724', + 'type': 'user', + 'url': 'https://tuchong.com/484724/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '484724', + 'sites': [], + 'tags': ['第三届京东摄影金像奖', 'JD最先锋'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/484724/53599767/', + 'views': 21848 + }, + { + 'author_id': '1193087', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 70, + 'content': '夏天的美少女\n\n你是我的一缕阳光', + 'created_at': '2019-08-18 15:39:49', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['分享神仙颜值'], + 'excerpt': '夏天的美少女\n\n你是我的一缕阳光', + 'favorite_list_prefix': [], + 'favorites': 1482, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 611226376, + 'img_id_str': '611226376', + 'title': '154', + 'user_id': 1193087, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 584094153, + 'img_id_str': '584094153', + 'title': '159', + 'user_id': 1193087, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 307597987, + 'img_id_str': '307597987', + 'title': '156', + 'user_id': 1193087, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 471371981, + 'img_id_str': '471371981', + 'title': '158', + 'user_id': 1193087, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 328700522, + 'img_id_str': '328700522', + 'title': '153', + 'user_id': 1193087, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 644387669, + 'img_id_str': '644387669', + 'title': '152', + 'user_id': 1193087, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 439259843, + 'img_id_str': '439259843', + 'title': '157', + 'user_id': 1193087, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3648, + 'img_id': 611029657, + 'img_id_str': '611029657', + 'title': '155', + 'user_id': 1193087, + 'width': 5472 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 469931111, + 'img_id_str': '469931111', + 'title': '160', + 'user_id': 1193087, + 'width': 3648 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '68', + 'passed_time': '2019年08月18日', + 'post_id': 49693874, + 'published_at': '2019-08-18 15:39:49', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 49, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 4911, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1193087_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '拍照小师傅KK', + 'site_id': '1193087', + 'type': 'user', + 'url': 'https://tuchong.com/1193087/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1193087', + 'sites': [], + 'tags': ['分享神仙颜值', '人像', '小清新', '色彩', '校园'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1193087/49693874/', + 'views': 57930 + }, + { + 'author_id': '1182492', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 60, + 'content': '猫猫', + 'created_at': '2019-08-14 10:27:57', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['分享神仙颜值'], + 'excerpt': '猫猫', + 'favorite_list_prefix': [], + 'favorites': 1457, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 381325548, + 'img_id_str': '381325548', + 'title': '001', + 'user_id': 1182492, + 'width': 3376 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 528650039, + 'img_id_str': '528650039', + 'title': '001', + 'user_id': 1182492, + 'width': 3376 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 145002274, + 'img_id_str': '145002274', + 'title': '001', + 'user_id': 1182492, + 'width': 3376 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5019, + 'img_id': 287281209, + 'img_id_str': '287281209', + 'title': '001', + 'user_id': 1182492, + 'width': 2823 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 433622596, + 'img_id_str': '433622596', + 'title': '001', + 'user_id': 1182492, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 590450465, + 'img_id_str': '590450465', + 'title': '001', + 'user_id': 1182492, + 'width': 3376 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 442994687, + 'img_id_str': '442994687', + 'title': '001', + 'user_id': 1182492, + 'width': 3376 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 442535854, + 'img_id_str': '442535854', + 'title': '001', + 'user_id': 1182492, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 45649480, + 'img_id_str': '45649480', + 'title': '001', + 'user_id': 1182492, + 'width': 6000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '52', + 'passed_time': '2019年08月14日', + 'post_id': 49106052, + 'published_at': '2019-08-14 10:27:57', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 45, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 13242, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1182492_6', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'KINGVISION', + 'site_id': '1182492', + 'type': 'user', + 'url': 'https://tuchong.com/1182492/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1182492', + 'sites': [], + 'tags': ['分享神仙颜值', '体操服', '少女写真', '校园', '人像', '同济大学'], + 'title': '体操服少女', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1182492/49106052/', + 'views': 50783 + }, + { + 'author_id': '1591194', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 9, + 'content': '', + 'created_at': '2020-01-10 19:55:34', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '航拍玩家俱乐部', + '一起去拍城市天际线', + '图虫旅行摄影圈子', + '这张天空必须要分享', + '绝不停止记录的2019', + '我要上开屏', + '我要上“首页推荐位”', + '带我看看,你的城市', + '一起拍建筑' + ], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 91, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5618, + 'img_id': 255051197, + 'img_id_str': '255051197', + 'title': '1226518', + 'user_id': 1591194, + 'width': 9586 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '8', + 'passed_time': '01月10日', + 'post_id': 61585195, + 'published_at': '2020-01-10 19:55:34', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '', + 'domain': 'sutong0845.tuchong.com', + 'followers': 110, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1591194_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '金刚蛋儿', + 'site_id': '1591194', + 'type': 'user', + 'url': 'https://sutong0845.tuchong.com/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '1591194', + 'sites': [], + 'tags': [ + '航拍玩家俱乐部', + '一起去拍城市天际线', + '图虫旅行摄影圈子', + '这张天空必须要分享', + '绝不停止记录的2019', + '我要上开屏' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://sutong0845.tuchong.com/61585195/', + 'views': 1651 + }, + { + 'author_id': '14244906', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': '新年伴手礼 奇趣曲奇', + 'created_at': '2020-01-05 17:49:17', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['生活小确幸', '静物美学', '拍拍好吃的', '生活太苦,吃点甜的'], + 'excerpt': '新年伴手礼 奇趣曲奇', + 'favorite_list_prefix': [], + 'favorites': 27, + 'image_count': 3, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 568444522, + 'img_id_str': '568444522', + 'title': '001', + 'user_id': 14244906, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 227001874, + 'img_id_str': '227001874', + 'title': '001', + 'user_id': 14244906, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 216450291, + 'img_id_str': '216450291', + 'title': '001', + 'user_id': 14244906, + 'width': 4000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月05日', + 'post_id': 61424205, + 'published_at': '2020-01-05 17:49:17', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深静物摄影师', + 'domain': '', + 'followers': 436, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_14244906_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '呆儿黄', + 'site_id': '14244906', + 'type': 'user', + 'url': 'https://tuchong.com/14244906/', + 'verification_list': [ + {'verification_reason': '资深静物摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '14244906', + 'sites': [], + 'tags': ['生活小确幸', '静物美学', '拍拍好吃的', '生活太苦,吃点甜的', '曲奇', '烘焙'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/14244906/61424205/', + 'views': 1512 + }, + { + 'author_id': '2680080', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 31, + 'content': '紫禁城建成600年\n试着用电影的感觉拍了一次故宫\n小年快乐☁️', + 'created_at': '2020-01-17 10:58:46', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '北京自然风光小组', + '中国地理摄影', + '图虫旅行摄影圈子', + '风光摄影圈', + '环球旅行摄影', + '最满意的风光照', + '我要上“首页推荐位”', + '每人推荐一个旅行目的地', + '带我看看,你的城市', + '闪迪旅拍大赛', + '我离开,又回到了故土——记录故乡与他乡', + '第五届镜头中的中国年' + ], + 'excerpt': '紫禁城建成600年\n试着用电影的感觉拍了一次故宫\n小年快乐☁️', + 'favorite_list_prefix': [], + 'favorites': 182, + 'image_count': 36, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5337, + 'img_id': 319735463, + 'img_id_str': '319735463', + 'title': '001', + 'user_id': 2680080, + 'width': 9488 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5337, + 'img_id': 431474259, + 'img_id_str': '431474259', + 'title': '001', + 'user_id': 2680080, + 'width': 9488 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5267, + 'img_id': 144164450, + 'img_id_str': '144164450', + 'title': '001', + 'user_id': 2680080, + 'width': 9364 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5341, + 'img_id': 227133252, + 'img_id_str': '227133252', + 'title': '001', + 'user_id': 2680080, + 'width': 9496 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5346, + 'img_id': 373867776, + 'img_id_str': '373867776', + 'title': '001', + 'user_id': 2680080, + 'width': 9503 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5338, + 'img_id': 373343987, + 'img_id_str': '373343987', + 'title': '001', + 'user_id': 2680080, + 'width': 9490 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3004, + 'img_id': 129747021, + 'img_id_str': '129747021', + 'title': '001', + 'user_id': 2680080, + 'width': 5341 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4844, + 'img_id': 380028386, + 'img_id_str': '380028386', + 'title': '001', + 'user_id': 2680080, + 'width': 8612 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5346, + 'img_id': 391890629, + 'img_id_str': '391890629', + 'title': '001', + 'user_id': 2680080, + 'width': 9504 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4790, + 'img_id': 177522334, + 'img_id_str': '177522334', + 'title': '001', + 'user_id': 2680080, + 'width': 8514 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5344, + 'img_id': 65849308, + 'img_id_str': '65849308', + 'title': '001', + 'user_id': 2680080, + 'width': 9499 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6868, + 'img_id': 607241811, + 'img_id_str': '607241811', + 'title': '001', + 'user_id': 2680080, + 'width': 12211 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5339, + 'img_id': 404408636, + 'img_id_str': '404408636', + 'title': '001', + 'user_id': 2680080, + 'width': 9491 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5342, + 'img_id': 399754282, + 'img_id_str': '399754282', + 'title': '001', + 'user_id': 2680080, + 'width': 9496 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5239, + 'img_id': 76466014, + 'img_id_str': '76466014', + 'title': '001', + 'user_id': 2680080, + 'width': 9315 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5338, + 'img_id': 480299283, + 'img_id_str': '480299283', + 'title': '001', + 'user_id': 2680080, + 'width': 9491 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5342, + 'img_id': 53462970, + 'img_id_str': '53462970', + 'title': '001', + 'user_id': 2680080, + 'width': 9496 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4852, + 'img_id': 329565945, + 'img_id_str': '329565945', + 'title': '001', + 'user_id': 2680080, + 'width': 8626 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 106940446, + 'img_id_str': '106940446', + 'title': '001', + 'user_id': 2680080, + 'width': 8534 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4727, + 'img_id': 603244282, + 'img_id_str': '603244282', + 'title': '001', + 'user_id': 2680080, + 'width': 8403 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5218, + 'img_id': 350733645, + 'img_id_str': '350733645', + 'title': '001', + 'user_id': 2680080, + 'width': 9276 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5332, + 'img_id': 341624457, + 'img_id_str': '341624457', + 'title': '001', + 'user_id': 2680080, + 'width': 9480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5289, + 'img_id': 112182655, + 'img_id_str': '112182655', + 'title': '001', + 'user_id': 2680080, + 'width': 9403 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5232, + 'img_id': 410306034, + 'img_id_str': '410306034', + 'title': '001', + 'user_id': 2680080, + 'width': 9301 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5342, + 'img_id': 362137558, + 'img_id_str': '362137558', + 'title': '001', + 'user_id': 2680080, + 'width': 9497 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5335, + 'img_id': 435013429, + 'img_id_str': '435013429', + 'title': '001', + 'user_id': 2680080, + 'width': 9485 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5343, + 'img_id': 410568246, + 'img_id_str': '410568246', + 'title': '001', + 'user_id': 2680080, + 'width': 9498 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5349, + 'img_id': 552191618, + 'img_id_str': '552191618', + 'title': '001', + 'user_id': 2680080, + 'width': 9509 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4885, + 'img_id': 205965407, + 'img_id_str': '205965407', + 'title': '001', + 'user_id': 2680080, + 'width': 8685 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5345, + 'img_id': 538822312, + 'img_id_str': '538822312', + 'title': '001', + 'user_id': 2680080, + 'width': 9501 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5340, + 'img_id': 267175454, + 'img_id_str': '267175454', + 'title': '001', + 'user_id': 2680080, + 'width': 9493 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5219, + 'img_id': 383108927, + 'img_id_str': '383108927', + 'title': '001', + 'user_id': 2680080, + 'width': 9278 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5254, + 'img_id': 327338072, + 'img_id_str': '327338072', + 'title': '001', + 'user_id': 2680080, + 'width': 9340 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5342, + 'img_id': 341493202, + 'img_id_str': '341493202', + 'title': '001', + 'user_id': 2680080, + 'width': 9496 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5334, + 'img_id': 197576110, + 'img_id_str': '197576110', + 'title': '001', + 'user_id': 2680080, + 'width': 9483 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5338, + 'img_id': 412403587, + 'img_id_str': '412403587', + 'title': '001', + 'user_id': 2680080, + 'width': 9490 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '21', + 'passed_time': '01月17日', + 'post_id': 61780318, + 'published_at': '2020-01-17 10:58:46', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 2, + 'site': { + 'description': '图虫图库销售达人', + 'domain': '', + 'followers': 4871, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2680080_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '北京豆包儿', + 'site_id': '2680080', + 'type': 'user', + 'url': 'https://tuchong.com/2680080/', + 'verification_list': [ + {'verification_reason': '图虫图库销售达人', 'verification_type': 14} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2680080', + 'sites': [], + 'tags': ['北京自然风光小组', '中国地理摄影', '图虫旅行摄影圈子', '风光摄影圈', '环球旅行摄影', '最满意的风光照'], + 'title': '紫禁城建成600年', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2680080/61780318/', + 'views': 5270 + }, + { + 'author_id': '363828', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 25, + 'content': '', + 'created_at': '2019-09-26 14:15:58', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 526, + 'image_count': 7, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 63154339, + 'img_id_str': '63154339', + 'title': '', + 'user_id': 363828, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1688, + 'img_id': 573941577, + 'img_id_str': '573941577', + 'title': '', + 'user_id': 363828, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 300853194, + 'img_id_str': '300853194', + 'title': '', + 'user_id': 363828, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 362653231, + 'img_id_str': '362653231', + 'title': '', + 'user_id': 363828, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 449226784, + 'img_id_str': '449226784', + 'title': '', + 'user_id': 363828, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 103392958, + 'img_id_str': '103392958', + 'title': '', + 'user_id': 363828, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 57518213, + 'img_id_str': '57518213', + 'title': '', + 'user_id': 363828, + 'width': 3000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '24', + 'passed_time': '2019年09月26日', + 'post_id': 53616046, + 'published_at': '2019-09-26 14:15:58', + 'recommend': true, + 'recom_type': '', + 'rewardable': false, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 8, + 'site': { + 'description': '他注定是个低调的大侠,所以什么都懒的写', + 'domain': '', + 'followers': 5098, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_363828_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '麻叉', + 'site_id': '363828', + 'type': 'user', + 'url': 'https://tuchong.com/363828/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '363828', + 'sites': [], + 'tags': ['人像', '小清新', '美女', '少女', '腿', '美腿'], + 'title': '旗袍-目', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/363828/53616046/', + 'views': 28414 + }, + { + 'author_id': '1725860', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '杭州过客', + 'created_at': '2020-01-14 10:29:10', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['带我看看,你的城市', '我要上开屏', '每人推荐一个旅行目的地'], + 'excerpt': '杭州过客', + 'favorite_list_prefix': [], + 'favorites': 22, + 'image_count': 14, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5000, + 'img_id': 579913184, + 'img_id_str': '579913184', + 'title': '452198', + 'user_id': 1725860, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5000, + 'img_id': 64866259, + 'img_id_str': '64866259', + 'title': '458237', + 'user_id': 1725860, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5000, + 'img_id': 235783693, + 'img_id_str': '235783693', + 'title': '458235', + 'user_id': 1725860, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4640, + 'img_id': 619365863, + 'img_id_str': '619365863', + 'title': '452202', + 'user_id': 1725860, + 'width': 3712 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5000, + 'img_id': 350406603, + 'img_id_str': '350406603', + 'title': '452153', + 'user_id': 1725860, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5000, + 'img_id': 622708459, + 'img_id_str': '622708459', + 'title': '456574', + 'user_id': 1725860, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5000, + 'img_id': 286771282, + 'img_id_str': '286771282', + 'title': '452201', + 'user_id': 1725860, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5000, + 'img_id': 275105230, + 'img_id_str': '275105230', + 'title': '452200', + 'user_id': 1725860, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5000, + 'img_id': 216712438, + 'img_id_str': '216712438', + 'title': '458236', + 'user_id': 1725860, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5000, + 'img_id': 293652042, + 'img_id_str': '293652042', + 'title': '454949', + 'user_id': 1725860, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 261670952, + 'img_id_str': '261670952', + 'title': '454948', + 'user_id': 1725860, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 619234925, + 'img_id_str': '619234925', + 'title': '452154', + 'user_id': 1725860, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2864, + 'img_id': 204064203, + 'img_id_str': '204064203', + 'title': '458238', + 'user_id': 1725860, + 'width': 5092 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3375, + 'img_id': 183944975, + 'img_id_str': '183944975', + 'title': '452199', + 'user_id': 1725860, + 'width': 6000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '01月14日', + 'post_id': 61694529, + 'published_at': '2020-01-14 10:29:10', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深风光摄影师', + 'domain': 'dminor1996.tuchong.com', + 'followers': 1040, + 'has_everphoto_note': false, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1725860_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'Dminor', + 'site_id': '1725860', + 'type': 'user', + 'url': 'https://dminor1996.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1725860', + 'sites': [], + 'tags': ['带我看看,你的城市', '我要上开屏', '每人推荐一个旅行目的地', '杭州', '城市', '旅行'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://dminor1996.tuchong.com/61694529/', + 'views': 1082 + }, + { + 'author_id': '11009227', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 53, + 'content': '霁雨', + 'created_at': '2019-11-23 10:38:05', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['对短发姑娘没有任何抵抗力!'], + 'excerpt': '霁雨', + 'favorite_list_prefix': [], + 'favorites': 877, + 'image_count': 16, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 610713130, + 'img_id_str': '610713130', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 586530602, + 'img_id_str': '586530602', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 378257441, + 'img_id_str': '378257441', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 69844971, + 'img_id_str': '69844971', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 367443839, + 'img_id_str': '367443839', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 131645462, + 'img_id_str': '131645462', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 418758721, + 'img_id_str': '418758721', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 310559092, + 'img_id_str': '310559092', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 51298605, + 'img_id_str': '51298605', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 374587351, + 'img_id_str': '374587351', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 438222671, + 'img_id_str': '438222671', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 249151157, + 'img_id_str': '249151157', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 532135415, + 'img_id_str': '532135415', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 283689398, + 'img_id_str': '283689398', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 234340570, + 'img_id_str': '234340570', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 43958540, + 'img_id_str': '43958540', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '50', + 'passed_time': '2019年11月23日', + 'post_id': 59127548, + 'published_at': '2019-11-23 10:38:05', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '15', + 'rqt_id': '', + 'shares': 55, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 9783, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_11009227_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '星野森', + 'site_id': '11009227', + 'type': 'user', + 'url': 'https://tuchong.com/11009227/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '11009227', + 'sites': [], + 'tags': ['对短发姑娘没有任何抵抗力!', '公园', '日系', '人像', '女孩', 'JK'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/11009227/59127548/', + 'views': 33807 + }, + { + 'author_id': '490904', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 24, + 'content': '夏天过去了', + 'created_at': '2019-10-21 12:06:05', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '高颜值女神聚集地', + '日系集', + '分享神仙颜值', + '人间四月天,找寻最美的你', + '人像爱好者', + '拍女友才是正经事' + ], + 'excerpt': '夏天过去了', + 'favorite_list_prefix': [], + 'favorites': 954, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 445752085, + 'img_id_str': '445752085', + 'title': '280472', + 'user_id': 490904, + 'width': 998 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 541439336, + 'img_id_str': '541439336', + 'title': '280485', + 'user_id': 490904, + 'width': 998 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 161002636, + 'img_id_str': '161002636', + 'title': '280483', + 'user_id': 490904, + 'width': 998 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 316782234, + 'img_id_str': '316782234', + 'title': '280477', + 'user_id': 490904, + 'width': 998 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 576238885, + 'img_id_str': '576238885', + 'title': '280473', + 'user_id': 490904, + 'width': 998 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 399094728, + 'img_id_str': '399094728', + 'title': '280471', + 'user_id': 490904, + 'width': 998 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 187671502, + 'img_id_str': '187671502', + 'title': '280467', + 'user_id': 490904, + 'width': 998 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 526366126, + 'img_id_str': '526366126', + 'title': '280468', + 'user_id': 490904, + 'width': 998 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 526169745, + 'img_id_str': '526169745', + 'title': '280470', + 'user_id': 490904, + 'width': 998 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '24', + 'passed_time': '2019年10月21日', + 'post_id': 56539579, + 'published_at': '2019-10-21 12:06:05', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 12, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 12649, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_490904_8', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影师CAT', + 'site_id': '490904', + 'type': 'user', + 'url': 'https://tuchong.com/490904/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '490904', + 'sites': [], + 'tags': [ + '高颜值女神聚集地', + '日系集', + '分享神仙颜值', + '人间四月天,找寻最美的你', + '人像爱好者', + '拍女友才是正经事' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/490904/56539579/', + 'views': 24764 + }, + { + 'author_id': '4518305', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 63, + 'content': + 'Sneak peak of a new aerial collection from the Highlands of Iceland I’m working on.\nMore images coming at the end of the week.\n\nThanks for watching.\nwww.marcograssiphotography', + 'created_at': '2019-12-19 03:30:49', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': + 'Sneak peak of a new aerial collection from the Highlands of Iceland I’m working on.\nMore images coming at the end of the week.\n\nThanks for watching.\nwww.marcograssiphotography', + 'favorite_list_prefix': [], + 'favorites': 850, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 115263139, + 'img_id_str': '115263139', + 'title': '', + 'user_id': 4518305, + 'width': 2001 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '62', + 'passed_time': '2019年12月19日', + 'post_id': 60675265, + 'published_at': '2019-12-19 03:30:49', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 30, + 'site': { + 'description': '环球旅拍风光摄影师', + 'domain': '', + 'followers': 10557, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_4518305_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'marcograssiphotography', + 'site_id': '4518305', + 'type': 'user', + 'url': 'https://tuchong.com/4518305/', + 'verification_list': [ + {'verification_reason': '环球旅拍风光摄影师', 'verification_type': 12} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '4518305', + 'sites': [], + 'tags': ['Landscape', 'travel', '自然', 'Iceland', 'nature', 'phototour'], + 'title': 'Highlands of Iceland', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/4518305/60675265/', + 'views': 12827 + }, + { + 'author_id': '2319253', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 69, + 'content': '?', + 'created_at': '2019-11-26 20:59:46', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['青岛圈子', '麦子儿童摄影课堂', '儿童摄影集', '有温度的人像'], + 'excerpt': '?', + 'favorite_list_prefix': [], + 'favorites': 1100, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 377995800, + 'img_id_str': '377995800', + 'title': '001', + 'user_id': 2319253, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 143049499, + 'img_id_str': '143049499', + 'title': '001', + 'user_id': 2319253, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 181387504, + 'img_id_str': '181387504', + 'title': '001', + 'user_id': 2319253, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 572112573, + 'img_id_str': '572112573', + 'title': '001', + 'user_id': 2319253, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 637191306, + 'img_id_str': '637191306', + 'title': '001', + 'user_id': 2319253, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 326483812, + 'img_id_str': '326483812', + 'title': '001', + 'user_id': 2319253, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 286441460, + 'img_id_str': '286441460', + 'title': '001', + 'user_id': 2319253, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 222084987, + 'img_id_str': '222084987', + 'title': '001', + 'user_id': 2319253, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 387104858, + 'img_id_str': '387104858', + 'title': '001', + 'user_id': 2319253, + 'width': 1500 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '69', + 'passed_time': '2019年11月26日', + 'post_id': 59396370, + 'published_at': '2019-11-26 20:59:46', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 45, + 'site': { + 'description': '资深儿童摄影师', + 'domain': '', + 'followers': 18644, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2319253_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '麦子先森', + 'site_id': '2319253', + 'type': 'user', + 'url': 'https://tuchong.com/2319253/', + 'verification_list': [ + {'verification_reason': '资深儿童摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2319253', + 'sites': [], + 'tags': ['青岛圈子', '麦子儿童摄影课堂', '儿童摄影集', '有温度的人像'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2319253/59396370/', + 'views': 34964 + }, + { + 'author_id': '3919111', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 7, + 'content': + '2020年,萤火虫动漫音乐节,胶衣双马尾护士\n照片拍摄于琶洲保利世贸博物馆\n出镜:i御十二\n摄影:赵潮文\n后期:赵潮文\n设备:5d4,50定焦f1.2\n#摄影#', + 'created_at': '2020-01-04 09:02:22', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['高颜值女神聚集地'], + 'excerpt': + '2020年,萤火虫动漫音乐节,胶衣双马尾护士\n照片拍摄于琶洲保利世贸博物馆\n出镜:i御十二\n摄影:赵潮文\n后期:赵潮文\n设备:5d4,50定焦f1.2\n#摄影#', + 'favorite_list_prefix': [], + 'favorites': 50, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 342869805, + 'img_id_str': '342869805', + 'title': '203806', + 'user_id': 3919111, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 616089081, + 'img_id_str': '616089081', + 'title': '203807', + 'user_id': 3919111, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 216843608, + 'img_id_str': '216843608', + 'title': '203809', + 'user_id': 3919111, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 60016106, + 'img_id_str': '60016106', + 'title': '203808', + 'user_id': 3919111, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 331335167, + 'img_id_str': '331335167', + 'title': '203810', + 'user_id': 3919111, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 113493537, + 'img_id_str': '113493537', + 'title': '203805', + 'user_id': 3919111, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 152094236, + 'img_id_str': '152094236', + 'title': '203813', + 'user_id': 3919111, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 46056855, + 'img_id_str': '46056855', + 'title': '203812', + 'user_id': 3919111, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 549701122, + 'img_id_str': '549701122', + 'title': '203811', + 'user_id': 3919111, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '5', + 'passed_time': '01月04日', + 'post_id': 61378749, + 'published_at': '2020-01-04 09:02:22', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 2, + 'site': { + 'description': '图虫签约摄影师,一位追梦路上的小哥哥!摄影约拍Q群:1021297879。', + 'domain': '', + 'followers': 362, + 'has_everphoto_note': false, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3919111_6', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '赵潮文CoCo', + 'site_id': '3919111', + 'type': 'user', + 'url': 'https://tuchong.com/3919111/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '3919111', + 'sites': [], + 'tags': ['高颜值女神聚集地', '人像', '美女', 'Cosplay', '二次元'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3919111/61378749/', + 'views': 4842 + }, + { + 'author_id': '2320813', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 6, + 'content': '明日方舟\n\n——\n\n凛冬', + 'created_at': '2020-01-10 08:37:40', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['富士Fujifilm圈子'], + 'excerpt': '明日方舟\n\n——\n\n凛冬', + 'favorite_list_prefix': [], + 'favorites': 119, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 54970287, + 'img_id_str': '54970287', + 'title': '550707', + 'user_id': 2320813, + 'width': 2880 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3072, + 'img_id': 635553289, + 'img_id_str': '635553289', + 'title': '550713', + 'user_id': 2320813, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3072, + 'img_id': 95077807, + 'img_id_str': '95077807', + 'title': '550711', + 'user_id': 2320813, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3072, + 'img_id': 302434183, + 'img_id_str': '302434183', + 'title': '550710', + 'user_id': 2320813, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3072, + 'img_id': 478528949, + 'img_id_str': '478528949', + 'title': '550709', + 'user_id': 2320813, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3072, + 'img_id': 96453845, + 'img_id_str': '96453845', + 'title': '550708', + 'user_id': 2320813, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3072, + 'img_id': 546096763, + 'img_id_str': '546096763', + 'title': '550714', + 'user_id': 2320813, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3414, + 'img_id': 124175893, + 'img_id_str': '124175893', + 'title': '550715', + 'user_id': 2320813, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 205899524, + 'img_id_str': '205899524', + 'title': '550705', + 'user_id': 2320813, + 'width': 1920 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '5', + 'passed_time': '01月10日', + 'post_id': 61567831, + 'published_at': '2020-01-10 08:37:40', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 7, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 2790, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2320813_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Season685', + 'site_id': '2320813', + 'type': 'user', + 'url': 'https://tuchong.com/2320813/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2320813', + 'sites': [], + 'tags': ['富士Fujifilm圈子', '特效', 'cos', 'Cosplay', '广州漫展', '明日方舟'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2320813/61567831/', + 'views': 6264 + }, + { + 'author_id': '11009227', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 35, + 'content': '咩啊', + 'created_at': '2020-01-10 11:54:54', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['对短发姑娘没有任何抵抗力!'], + 'excerpt': '咩啊', + 'favorite_list_prefix': [], + 'favorites': 477, + 'image_count': 13, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 544983151, + 'img_id_str': '544983151', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 543344154, + 'img_id_str': '543344154', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 153339906, + 'img_id_str': '153339906', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 551077526, + 'img_id_str': '551077526', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2488, + 'img_id': 369936400, + 'img_id_str': '369936400', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 297256528, + 'img_id_str': '297256528', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 538363645, + 'img_id_str': '538363645', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 136628574, + 'img_id_str': '136628574', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 39896537, + 'img_id_str': '39896537', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 435865126, + 'img_id_str': '435865126', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 579978622, + 'img_id_str': '579978622', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 105694628, + 'img_id_str': '105694628', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 597149632, + 'img_id_str': '597149632', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '31', + 'passed_time': '01月10日', + 'post_id': 61572763, + 'published_at': '2020-01-10 11:54:54', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '10', + 'rqt_id': '', + 'shares': 31, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 9783, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_11009227_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '星野森', + 'site_id': '11009227', + 'type': 'user', + 'url': 'https://tuchong.com/11009227/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '11009227', + 'sites': [], + 'tags': ['对短发姑娘没有任何抵抗力!', '人像', '女孩', '街拍', 'JK', 'jk写真'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/11009227/61572763/', + 'views': 31395 + }, + { + 'author_id': '2600543', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 5, + 'content': '出境:@苏嫣嫣阿姨 \n后期:@苏嫣呐', + 'created_at': '2019-12-20 11:10:56', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '高颜值女神聚集地', + 'Cosplay摄影集中地', + '让摄影穿破次元壁', + '深大摄影交流群', + '图虫约拍圈子', + '飞图摄影学院评片会' + ], + 'excerpt': '出境:@苏嫣嫣阿姨 \n后期:@苏嫣呐', + 'favorite_list_prefix': [], + 'favorites': 225, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 626705241, + 'img_id_str': '626705241', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 92521753, + 'img_id_str': '92521753', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 568378899, + 'img_id_str': '568378899', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 188663079, + 'img_id_str': '188663079', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 642172217, + 'img_id_str': '642172217', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4672, + 'img_id': 475973411, + 'img_id_str': '475973411', + 'title': '', + 'user_id': 2600543, + 'width': 3503 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 521651278, + 'img_id_str': '521651278', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 56804397, + 'img_id_str': '56804397', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 546358489, + 'img_id_str': '546358489', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '5', + 'passed_time': '2019年12月20日', + 'post_id': 60727218, + 'published_at': '2019-12-20 11:10:56', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 4, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': 'sanyue015.tuchong.com', + 'followers': 13818, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2600543_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '叁月life', + 'site_id': '2600543', + 'type': 'user', + 'url': 'https://sanyue015.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2600543', + 'sites': [], + 'tags': [ + '高颜值女神聚集地', + 'Cosplay摄影集中地', + '让摄影穿破次元壁', + '深大摄影交流群', + '图虫约拍圈子', + '飞图摄影学院评片会' + ], + 'title': '#王者荣耀 #圣诞 #貂蝉圣诞恋歌 #深圳约拍', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://sanyue015.tuchong.com/60727218/', + 'views': 11607 + }, + { + 'author_id': '1953020', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '', + 'created_at': '2019-12-20 21:16:55', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['2019你最满意的照片'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 168, + 'image_count': 49, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 340641088, + 'img_id_str': '340641088', + 'title': '', + 'user_id': 1953020, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 274056323, + 'img_id_str': '274056323', + 'title': '', + 'user_id': 1953020, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3780, + 'img_id': 499630956, + 'img_id_str': '499630956', + 'title': '', + 'user_id': 1953020, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 505988437, + 'img_id_str': '505988437', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 432064026, + 'img_id_str': '432064026', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 216384895, + 'img_id_str': '216384895', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3668, + 'img_id': 75482017, + 'img_id_str': '75482017', + 'title': '', + 'user_id': 1953020, + 'width': 6521 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 162120591, + 'img_id_str': '162120591', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 529031760, + 'img_id_str': '529031760', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 195609852, + 'img_id_str': '195609852', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 242140618, + 'img_id_str': '242140618', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 102452553, + 'img_id_str': '102452553', + 'title': '', + 'user_id': 1953020, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 322618654, + 'img_id_str': '322618654', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 591428097, + 'img_id_str': '591428097', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 490587279, + 'img_id_str': '490587279', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 106313040, + 'img_id_str': '106313040', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 171295669, + 'img_id_str': '171295669', + 'title': '', + 'user_id': 1953020, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 142656367, + 'img_id_str': '142656367', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6025, + 'img_id': 529319105, + 'img_id_str': '529319105', + 'title': '', + 'user_id': 1953020, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 560841764, + 'img_id_str': '560841764', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 133645270, + 'img_id_str': '133645270', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3525, + 'img_id': 221937510, + 'img_id_str': '221937510', + 'title': '', + 'user_id': 1953020, + 'width': 6267 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3586, + 'img_id': 293258869, + 'img_id_str': '293258869', + 'title': '', + 'user_id': 1953020, + 'width': 6373 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4237, + 'img_id': 358532421, + 'img_id_str': '358532421', + 'title': '', + 'user_id': 1953020, + 'width': 6355 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 448372047, + 'img_id_str': '448372047', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3422, + 'img_id': 529122315, + 'img_id_str': '529122315', + 'title': '', + 'user_id': 1953020, + 'width': 6083 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 572310533, + 'img_id_str': '572310533', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3780, + 'img_id': 337298674, + 'img_id_str': '337298674', + 'title': '', + 'user_id': 1953020, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 149209957, + 'img_id_str': '149209957', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 56804408, + 'img_id_str': '56804408', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 625657016, + 'img_id_str': '625657016', + 'title': '', + 'user_id': 1953020, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 448447802, + 'img_id_str': '448447802', + 'title': '', + 'user_id': 1953020, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 587379966, + 'img_id_str': '587379966', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4282, + 'img_id': 547013666, + 'img_id_str': '547013666', + 'title': '', + 'user_id': 1953020, + 'width': 6424 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 311543186, + 'img_id_str': '311543186', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 401916992, + 'img_id_str': '401916992', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 183158125, + 'img_id_str': '183158125', + 'title': '', + 'user_id': 1953020, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 448381903, + 'img_id_str': '448381903', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 480494802, + 'img_id_str': '480494802', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3780, + 'img_id': 182764972, + 'img_id_str': '182764972', + 'title': '', + 'user_id': 1953020, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 282314107, + 'img_id_str': '282314107', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 220579107, + 'img_id_str': '220579107', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 127321728, + 'img_id_str': '127321728', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5403, + 'img_id': 167757034, + 'img_id_str': '167757034', + 'title': '', + 'user_id': 1953020, + 'width': 3602 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 503301216, + 'img_id_str': '503301216', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 78037741, + 'img_id_str': '78037741', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3780, + 'img_id': 621528085, + 'img_id_str': '621528085', + 'title': '', + 'user_id': 1953020, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 581420617, + 'img_id_str': '581420617', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 472565139, + 'img_id_str': '472565139', + 'title': '', + 'user_id': 1953020, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '4', + 'passed_time': '2019年12月20日', + 'post_id': 60750302, + 'published_at': '2019-12-20 21:16:55', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 3, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 1974, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1953020_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '三咩咩', + 'site_id': '1953020', + 'type': 'user', + 'url': 'https://tuchong.com/1953020/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1953020', + 'sites': [], + 'tags': ['2019你最满意的照片', '人像', '美女', '汉服', '古风', '佳能'], + 'title': '我的2019', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1953020/60750302/', + 'views': 5707 + }, + { + 'author_id': '1515046', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '大理旅拍|\n\n山朝我们走来,将夕阳放在你的发梢。', + 'created_at': '2020-01-09 16:32:16', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['人像爱好者'], + 'excerpt': '大理旅拍|\n\n山朝我们走来,将夕阳放在你的发梢。', + 'favorite_list_prefix': [], + 'favorites': 30, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 61654866, + 'img_id_str': '61654866', + 'title': '001', + 'user_id': 1515046, + 'width': 844 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 489473778, + 'img_id_str': '489473778', + 'title': '001', + 'user_id': 1515046, + 'width': 844 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 281724551, + 'img_id_str': '281724551', + 'title': '001', + 'user_id': 1515046, + 'width': 844 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 67028441, + 'img_id_str': '67028441', + 'title': '001', + 'user_id': 1515046, + 'width': 844 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 558220861, + 'img_id_str': '558220861', + 'title': '001', + 'user_id': 1515046, + 'width': 1688 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1181, + 'img_id': 645842691, + 'img_id_str': '645842691', + 'title': '001', + 'user_id': 1515046, + 'width': 665 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 498648894, + 'img_id_str': '498648894', + 'title': '001', + 'user_id': 1515046, + 'width': 1333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 577160774, + 'img_id_str': '577160774', + 'title': '001', + 'user_id': 1515046, + 'width': 844 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 361023396, + 'img_id_str': '361023396', + 'title': '001', + 'user_id': 1515046, + 'width': 844 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '01月09日', + 'post_id': 61549663, + 'published_at': '2020-01-09 16:32:16', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深婚礼婚纱摄影师', + 'domain': '', + 'followers': 1089, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1515046_6', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '25号底片摄影工作室', + 'site_id': '1515046', + 'type': 'user', + 'url': 'https://tuchong.com/1515046/', + 'verification_list': [ + {'verification_reason': '资深婚礼婚纱摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1515046', + 'sites': [], + 'tags': ['人像爱好者', '爱', '婚纱', '旅拍'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1515046/61549663/', + 'views': 1793 + }, + { + 'author_id': '3787347', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '年代', + 'created_at': '2020-01-07 13:45:50', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '长沙人像写真', + '我要上“首页推荐位”', + '儿童摄影集', + '胶片人像摄影', + '谁还没点童年黑照', + '分享神仙颜值', + '暖色调人像', + '河北摄影圈', + '高颜值女神聚集地' + ], + 'excerpt': '年代', + 'favorite_list_prefix': [], + 'favorites': 49, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1200, + 'img_id': 490126961, + 'img_id_str': '490126961', + 'title': '1602186', + 'user_id': 3787347, + 'width': 1800 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月07日', + 'post_id': 61484310, + 'published_at': '2020-01-07 13:45:50', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深儿童摄影师', + 'domain': '', + 'followers': 3899, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3787347_7', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '儿童摄影师灰太狼', + 'site_id': '3787347', + 'type': 'user', + 'url': 'https://tuchong.com/3787347/', + 'verification_list': [ + {'verification_reason': '资深儿童摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3787347', + 'sites': [], + 'tags': ['长沙人像写真', '我要上“首页推荐位”', '儿童摄影集', '胶片人像摄影', '谁还没点童年黑照', '分享神仙颜值'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3787347/61484310/', + 'views': 1983 + }, + { + 'author_id': '15957016', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '', + 'created_at': '2020-01-20 08:48:01', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['生活小确幸'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 7, + 'image_count': 16, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 110479286, + 'img_id_str': '110479286', + 'title': '239168', + 'user_id': 15957016, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 615, + 'img_id': 64931519, + 'img_id_str': '64931519', + 'title': '4365', + 'user_id': 15957016, + 'width': 740 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 300468383, + 'img_id_str': '300468383', + 'title': '5772', + 'user_id': 15957016, + 'width': 2592 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2592, + 'img_id': 103663110, + 'img_id_str': '103663110', + 'title': '5567', + 'user_id': 15957016, + 'width': 4608 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2592, + 'img_id': 472893406, + 'img_id_str': '472893406', + 'title': '5469', + 'user_id': 15957016, + 'width': 4608 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 531744212, + 'img_id_str': '531744212', + 'title': '4990', + 'user_id': 15957016, + 'width': 2592 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 119326369, + 'img_id_str': '119326369', + 'title': '4986', + 'user_id': 15957016, + 'width': 2592 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 562874035, + 'img_id_str': '562874035', + 'title': '4776', + 'user_id': 15957016, + 'width': 2592 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 61655027, + 'img_id_str': '61655027', + 'title': '4768', + 'user_id': 15957016, + 'width': 2592 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2592, + 'img_id': 173656159, + 'img_id_str': '173656159', + 'title': '4725', + 'user_id': 15957016, + 'width': 4608 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 417056399, + 'img_id_str': '417056399', + 'title': '4726', + 'user_id': 15957016, + 'width': 2592 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2592, + 'img_id': 615040544, + 'img_id_str': '615040544', + 'title': '4719', + 'user_id': 15957016, + 'width': 4608 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 476038554, + 'img_id_str': '476038554', + 'title': '4661', + 'user_id': 15957016, + 'width': 2592 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1280, + 'img_id': 212125603, + 'img_id_str': '212125603', + 'title': '4631', + 'user_id': 15957016, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 358074513, + 'img_id_str': '358074513', + 'title': '5820', + 'user_id': 15957016, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1200, + 'img_id': 582338108, + 'img_id_str': '582338108', + 'title': '5506', + 'user_id': 15957016, + 'width': 900 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '8小时前', + 'post_id': 61858782, + 'published_at': '2020-01-20 08:48:01', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 8, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15957016_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'MM245211', + 'site_id': '15957016', + 'type': 'user', + 'url': 'https://tuchong.com/15957016/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15957016', + 'sites': [], + 'tags': ['生活小确幸', '房子'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15957016/61858782/', + 'views': 361 + }, + { + 'author_id': '15960591', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 5, + 'content': '美的不是太阳,是光,是影,是人', + 'created_at': '2020-01-19 22:37:32', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['夕阳捕手'], + 'excerpt': '美的不是太阳,是光,是影,是人', + 'favorite_list_prefix': [], + 'favorites': 8, + 'image_count': 3, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2773, + 'img_id': 484427604, + 'img_id_str': '484427604', + 'title': '837888', + 'user_id': 15960591, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4160, + 'img_id': 427018134, + 'img_id_str': '427018134', + 'title': '748739', + 'user_id': 15960591, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4160, + 'img_id': 595839056, + 'img_id_str': '595839056', + 'title': '724998', + 'user_id': 15960591, + 'width': 3120 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '19小时前', + 'post_id': 61850552, + 'published_at': '2020-01-19 22:37:32', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 2, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15960591_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '清秋却惊寒', + 'site_id': '15960591', + 'type': 'user', + 'url': 'https://tuchong.com/15960591/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15960591', + 'sites': [], + 'tags': ['夕阳捕手'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15960591/61850552/', + 'views': 458 + }, + { + 'author_id': '15960226', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '', + 'created_at': '2020-01-19 21:49:15', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['广州摄影圈'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 10, + 'image_count': 3, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3829, + 'img_id': 172280218, + 'img_id_str': '172280218', + 'title': '001', + 'user_id': 15960226, + 'width': 5744 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3699, + 'img_id': 302172196, + 'img_id_str': '302172196', + 'title': '001', + 'user_id': 15960226, + 'width': 5549 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5761, + 'img_id': 389335332, + 'img_id_str': '389335332', + 'title': '001', + 'user_id': 15960226, + 'width': 3840 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '19小时前', + 'post_id': 61849117, + 'published_at': '2020-01-19 21:49:15', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 2, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15960226_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'garyXC', + 'site_id': '15960226', + 'type': 'user', + 'url': 'https://tuchong.com/15960226/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15960226', + 'sites': [], + 'tags': ['广州摄影圈', '索尼a6400'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15960226/61849117/', + 'views': 360 + }, + { + 'author_id': '15939785', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '🏮小年里的前门·夕阳将落,华灯初上。', + 'created_at': '2020-01-19 20:46:17', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '带我看看,你的城市', + '拿起手机人人都是摄影师', + '中国手机摄影', + '生活小确幸', + '我用手机修照片', + '火烛一花精品摄影' + ], + 'excerpt': '🏮小年里的前门·夕阳将落,华灯初上。', + 'favorite_list_prefix': [], + 'favorites': 15, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 210748996, + 'img_id_str': '210748996', + 'title': '001', + 'user_id': 15939785, + 'width': 3024 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3425, + 'img_id': 347981454, + 'img_id_str': '347981454', + 'title': '001', + 'user_id': 15939785, + 'width': 2569 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 577160915, + 'img_id_str': '577160915', + 'title': '001', + 'user_id': 15939785, + 'width': 3024 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 115590958, + 'img_id_str': '115590958', + 'title': '001', + 'user_id': 15939785, + 'width': 3024 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 529451170, + 'img_id_str': '529451170', + 'title': '001', + 'user_id': 15939785, + 'width': 3024 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3459, + 'img_id': 508675832, + 'img_id_str': '508675832', + 'title': '001', + 'user_id': 15939785, + 'width': 2173 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 100189501, + 'img_id_str': '100189501', + 'title': '001', + 'user_id': 15939785, + 'width': 2309 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3819, + 'img_id': 293193506, + 'img_id_str': '293193506', + 'title': '001', + 'user_id': 15939785, + 'width': 2268 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3653, + 'img_id': 37144476, + 'img_id_str': '37144476', + 'title': '001', + 'user_id': 15939785, + 'width': 2268 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3740, + 'img_id': 453887529, + 'img_id_str': '453887529', + 'title': '001', + 'user_id': 15939785, + 'width': 2268 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3643, + 'img_id': 326027388, + 'img_id_str': '326027388', + 'title': '001', + 'user_id': 15939785, + 'width': 2268 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 287360962, + 'img_id_str': '287360962', + 'title': '001', + 'user_id': 15939785, + 'width': 2268 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '20小时前', + 'post_id': 61847117, + 'published_at': '2020-01-19 20:46:17', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 10, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15939785_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '王力宏de手绘', + 'site_id': '15939785', + 'type': 'user', + 'url': 'https://tuchong.com/15939785/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15939785', + 'sites': [], + 'tags': [ + '带我看看,你的城市', + '拿起手机人人都是摄影师', + '中国手机摄影', + '生活小确幸', + '我用手机修照片', + '火烛一花精品摄影' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15939785/61847117/', + 'views': 700 + }, + { + 'author_id': '15959170', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '宋城旧照', + 'created_at': '2020-01-19 20:04:33', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['我们都爱黑白摄影'], + 'excerpt': '宋城旧照', + 'favorite_list_prefix': [], + 'favorites': 10, + 'image_count': 7, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3120, + 'img_id': 117426494, + 'img_id_str': '117426494', + 'title': '1021874', + 'user_id': 15959170, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3120, + 'img_id': 583517796, + 'img_id_str': '583517796', + 'title': '1021875', + 'user_id': 15959170, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3120, + 'img_id': 149997005, + 'img_id_str': '149997005', + 'title': '1021876', + 'user_id': 15959170, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3120, + 'img_id': 72403030, + 'img_id_str': '72403030', + 'title': '1021877', + 'user_id': 15959170, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3120, + 'img_id': 424003381, + 'img_id_str': '424003381', + 'title': '1021878', + 'user_id': 15959170, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3120, + 'img_id': 601147093, + 'img_id_str': '601147093', + 'title': '1021879', + 'user_id': 15959170, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3120, + 'img_id': 609928818, + 'img_id_str': '609928818', + 'title': '1021880', + 'user_id': 15959170, + 'width': 4160 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '21小时前', + 'post_id': 61846021, + 'published_at': '2020-01-19 20:04:33', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 2, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15959170_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '初夏夜未泯', + 'site_id': '15959170', + 'type': 'user', + 'url': 'https://tuchong.com/15959170/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15959170', + 'sites': [], + 'tags': ['我们都爱黑白摄影', '街道', '黑白'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15959170/61846021/', + 'views': 338 + }, + { + 'author_id': '13483557', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 3, + 'content': '', + 'created_at': '2020-01-19 17:40:11', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['火烛一花精品摄影'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 13, + 'image_count': 3, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 240371730, + 'img_id_str': '240371730', + 'title': '1268999', + 'user_id': 13483557, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 549111565, + 'img_id_str': '549111565', + 'title': '2031828', + 'user_id': 13483557, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3456, + 'img_id': 244762691, + 'img_id_str': '244762691', + 'title': '1892638', + 'user_id': 13483557, + 'width': 4608 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月19日', + 'post_id': 61842611, + 'published_at': '2020-01-19 17:40:11', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 2, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_13483557_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '叶子何时', + 'site_id': '13483557', + 'type': 'user', + 'url': 'https://tuchong.com/13483557/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '13483557', + 'sites': [], + 'tags': ['火烛一花精品摄影', '绽放', '莲花', '池塘', '花'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/13483557/61842611/', + 'views': 458 + }, + { + 'author_id': '15958327', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '常记花亭日暮,水光山色无数。斜阳满沙渚,何来渔舟可渡。且住,且住,惊散游鱼无数。\n\n——如梦令·花亭湖', + 'created_at': '2020-01-19 16:40:07', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['安徽省摄影家俱乐部'], + 'excerpt': '常记花亭日暮,水光山色无数。斜阳满沙渚,何来渔舟可渡。且住,且住,惊散游鱼无数。\n\n——如梦令·花亭湖', + 'favorite_list_prefix': [], + 'favorites': 13, + 'image_count': 3, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2227, + 'img_id': 471518019, + 'img_id_str': '471518019', + 'title': '001', + 'user_id': 15958327, + 'width': 3242 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2448, + 'img_id': 247252427, + 'img_id_str': '247252427', + 'title': '001', + 'user_id': 15958327, + 'width': 3264 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 216123475, + 'img_id_str': '216123475', + 'title': '001', + 'user_id': 15958327, + 'width': 2448 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月19日', + 'post_id': 61841098, + 'published_at': '2020-01-19 16:40:07', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 6, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15958327_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '唐怀瑾', + 'site_id': '15958327', + 'type': 'user', + 'url': 'https://tuchong.com/15958327/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15958327', + 'sites': [], + 'tags': ['安徽省摄影家俱乐部'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15958327/61841098/', + 'views': 633 + }, + { + 'author_id': '15948666', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '扫街', + 'created_at': '2020-01-19 16:14:11', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['风光摄影圈', '带我看看,你的城市', '一个风光小组ONE'], + 'excerpt': '扫街', + 'favorite_list_prefix': [], + 'favorites': 13, + 'image_count': 4, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5477, + 'img_id': 224839324, + 'img_id_str': '224839324', + 'title': '1706218', + 'user_id': 15948666, + 'width': 3651 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5477, + 'img_id': 310363619, + 'img_id_str': '310363619', + 'title': '1706226', + 'user_id': 15948666, + 'width': 3651 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3651, + 'img_id': 356632463, + 'img_id_str': '356632463', + 'title': '1706227', + 'user_id': 15948666, + 'width': 5477 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5477, + 'img_id': 265406934, + 'img_id_str': '265406934', + 'title': '1706228', + 'user_id': 15948666, + 'width': 3651 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月19日', + 'post_id': 61840507, + 'published_at': '2020-01-19 16:14:11', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 2, + 'site': { + 'description': '', + 'domain': '', + 'followers': 3, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15948666_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '自由落体551', + 'site_id': '15948666', + 'type': 'user', + 'url': 'https://tuchong.com/15948666/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15948666', + 'sites': [], + 'tags': ['风光摄影圈', '带我看看,你的城市', '一个风光小组ONE'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15948666/61840507/', + 'views': 340 + }, + { + 'author_id': '15943121', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '', + 'created_at': '2020-01-19 13:46:52', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['街拍俱乐部圈子'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 15, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3024, + 'img_id': 571065802, + 'img_id_str': '571065802', + 'title': '001', + 'user_id': 15943121, + 'width': 4032 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月19日', + 'post_id': 61837294, + 'published_at': '2020-01-19 13:46:52', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '做自己不喜欢的事 怎么会快乐呢', + 'domain': '', + 'followers': 5, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15943121_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '快快啊', + 'site_id': '15943121', + 'type': 'user', + 'url': 'https://tuchong.com/15943121/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15943121', + 'sites': [], + 'tags': ['街拍俱乐部圈子', '复古'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15943121/61837294/', + 'views': 630 + }, + { + 'author_id': '15955064', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '燃烧的☁️', + 'created_at': '2020-01-19 13:14:37', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['带我看看,你的城市', '这张天空必须要分享', '我们都是城市探险家', '光影者的风光摄影'], + 'excerpt': '燃烧的☁️', + 'favorite_list_prefix': [], + 'favorites': 13, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 249677215, + 'img_id_str': '249677215', + 'title': '57642', + 'user_id': 15955064, + 'width': 2133 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 241878906, + 'img_id_str': '241878906', + 'title': '57643', + 'user_id': 15955064, + 'width': 2133 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月19日', + 'post_id': 61836583, + 'published_at': '2020-01-19 13:14:37', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 6, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15955064_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '不会照相的瑞仔', + 'site_id': '15955064', + 'type': 'user', + 'url': 'https://tuchong.com/15955064/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15955064', + 'sites': [], + 'tags': ['带我看看,你的城市', '这张天空必须要分享', '我们都是城市探险家', '光影者的风光摄影', '天空'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15955064/61836583/', + 'views': 538 + }, + { + 'author_id': '15945305', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '说不明的感觉。只能说这个感觉、这个角度、很心水、就拍下了。', + 'created_at': '2020-01-19 11:57:08', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['一起去拍城市天际线'], + 'excerpt': '说不明的感觉。只能说这个感觉、这个角度、很心水、就拍下了。', + 'favorite_list_prefix': [], + 'favorites': 11, + 'image_count': 3, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3456, + 'img_id': 225953919, + 'img_id_str': '225953919', + 'title': '74277', + 'user_id': 15945305, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2176, + 'img_id': 53659602, + 'img_id_str': '53659602', + 'title': '74278', + 'user_id': 15945305, + 'width': 4608 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 491636280, + 'img_id_str': '491636280', + 'title': '74276', + 'user_id': 15945305, + 'width': 2176 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月19日', + 'post_id': 61835127, + 'published_at': '2020-01-19 11:57:08', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 6, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15945305_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '莫名用户', + 'site_id': '15945305', + 'type': 'user', + 'url': 'https://tuchong.com/15945305/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15945305', + 'sites': [], + 'tags': ['一起去拍城市天际线', '禅宗', '寺庙'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15945305/61835127/', + 'views': 565 + }, + { + 'author_id': '15956920', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 5, + 'content': '', + 'created_at': '2020-01-19 11:49:40', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['度假就要去海滩'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 15, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 166512201, + 'img_id_str': '166512201', + 'title': '001', + 'user_id': 15956920, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 463324971, + 'img_id_str': '463324971', + 'title': '001', + 'user_id': 15956920, + 'width': 6000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '01月19日', + 'post_id': 61834985, + 'published_at': '2020-01-19 11:49:40', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 7, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15956920_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '方荣华', + 'site_id': '15956920', + 'type': 'user', + 'url': 'https://tuchong.com/15956920/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15956920', + 'sites': [], + 'tags': ['度假就要去海滩', '黎明'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15956920/61834985/', + 'views': 566 + }, + { + 'author_id': '15954097', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '军魂永驻', + 'created_at': '2020-01-18 20:58:05', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['最满意的风光照'], + 'excerpt': '军魂永驻', + 'favorite_list_prefix': [], + 'favorites': 16, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 502384401, + 'img_id_str': '502384401', + 'title': '001', + 'user_id': 15954097, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 366790353, + 'img_id_str': '366790353', + 'title': '001', + 'user_id': 15954097, + 'width': 6000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月18日', + 'post_id': 61820979, + 'published_at': '2020-01-18 20:58:05', + 'recommend': true, + 'recom_type': '', + 'rewardable': false, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '只为就下那一瞬间~', + 'domain': '', + 'followers': 12, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15954097_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Rendering143', + 'site_id': '15954097', + 'type': 'user', + 'url': 'https://tuchong.com/15954097/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15954097', + 'sites': [], + 'tags': ['最满意的风光照', '背光', '天空', '太阳', '军人'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15954097/61820979/', + 'views': 704 + }, + { + 'author_id': '15950662', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 3, + 'content': '雪后#济南*大明湖', + 'created_at': '2020-01-18 14:16:31', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '中国手机摄影', + '拿起手机人人都是摄影师', + '手机摄影小分队', + '取景器里的小美好', + '华为摄影爱好者', + '带我看看,你的城市' + ], + 'excerpt': '雪后#济南*大明湖', + 'favorite_list_prefix': [], + 'favorites': 17, + 'image_count': 6, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 176605134, + 'img_id_str': '176605134', + 'title': '399488', + 'user_id': 15950662, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 33736528, + 'img_id_str': '33736528', + 'title': '399506', + 'user_id': 15950662, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1392, + 'img_id': 487114222, + 'img_id_str': '487114222', + 'title': '399493', + 'user_id': 15950662, + 'width': 1716 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 147506822, + 'img_id_str': '147506822', + 'title': '399490', + 'user_id': 15950662, + 'width': 1620 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 92784607, + 'img_id_str': '92784607', + 'title': '399498', + 'user_id': 15950662, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 193447737, + 'img_id_str': '193447737', + 'title': '399489', + 'user_id': 15950662, + 'width': 2160 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月18日', + 'post_id': 61811702, + 'published_at': '2020-01-18 14:16:31', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '用镜头记录生活中的每一个美好瞬间~', + 'domain': '', + 'followers': 8, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15950662_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '小晓雅', + 'site_id': '15950662', + 'type': 'user', + 'url': 'https://tuchong.com/15950662/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15950662', + 'sites': [], + 'tags': [ + '中国手机摄影', + '拿起手机人人都是摄影师', + '手机摄影小分队', + '取景器里的小美好', + '华为摄影爱好者', + '带我看看,你的城市' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15950662/61811702/', + 'views': 547 + }, + { + 'author_id': '1781920', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 54, + 'content': '秋の思绪', + 'created_at': '2019-10-03 00:11:38', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '武汉日系摄影师联盟', + '写真人像摄影', + '暖色调人像', + '日系集', + '人像摄影集', + '我们都爱日系摄影', + '分享神仙颜值', + '我要上开屏', + '糖水人像小组' + ], + 'excerpt': '秋の思绪', + 'favorite_list_prefix': [], + 'favorites': 638, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2879, + 'img_id': 473476209, + 'img_id_str': '473476209', + 'title': '1613968', + 'user_id': 1781920, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 395422808, + 'img_id_str': '395422808', + 'title': '1613958', + 'user_id': 1781920, + 'width': 1281 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1281, + 'img_id': 345353172, + 'img_id_str': '345353172', + 'title': '1613960', + 'user_id': 1781920, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 43691277, + 'img_id_str': '43691277', + 'title': '1613957', + 'user_id': 1781920, + 'width': 1281 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 581807127, + 'img_id_str': '581807127', + 'title': '1613959', + 'user_id': 1781920, + 'width': 1281 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 278440499, + 'img_id_str': '278440499', + 'title': '1613956', + 'user_id': 1781920, + 'width': 1281 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1281, + 'img_id': 540846970, + 'img_id_str': '540846970', + 'title': '1613962', + 'user_id': 1781920, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1281, + 'img_id': 74033694, + 'img_id_str': '74033694', + 'title': '1613769', + 'user_id': 1781920, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 91990873, + 'img_id_str': '91990873', + 'title': '1613764', + 'user_id': 1781920, + 'width': 1281 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1281, + 'img_id': 560508417, + 'img_id_str': '560508417', + 'title': '1613765', + 'user_id': 1781920, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1281, + 'img_id': 90942438, + 'img_id_str': '90942438', + 'title': '1613771', + 'user_id': 1781920, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1281, + 'img_id': 604155042, + 'img_id_str': '604155042', + 'title': '1613971', + 'user_id': 1781920, + 'width': 1920 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '29', + 'passed_time': '2019年10月03日', + 'post_id': 54352347, + 'published_at': '2019-10-03 00:11:38', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 25, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 3650, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1781920_4', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '背相机的大熊', + 'site_id': '1781920', + 'type': 'user', + 'url': 'https://tuchong.com/1781920/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1781920', + 'sites': [], + 'tags': ['武汉日系摄影师联盟', '写真人像摄影', '暖色调人像', '日系集', '人像摄影集', '我们都爱日系摄影'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1781920/54352347/', + 'views': 21322 + }, + { + 'author_id': '15948588', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '科技之光', + 'created_at': '2020-01-19 15:28:58', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['中国手机摄影'], + 'excerpt': '科技之光', + 'favorite_list_prefix': [], + 'favorites': 9, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4351, + 'img_id': 522766401, + 'img_id_str': '522766401', + 'title': '923543', + 'user_id': 15948588, + 'width': 3456 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月19日', + 'post_id': 61839461, + 'published_at': '2020-01-19 15:28:58', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '鲜花、美酒、美人,而我只喜欢你', + 'domain': '', + 'followers': 5, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15948588_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '朝夏与酒', + 'site_id': '15948588', + 'type': 'user', + 'url': 'https://tuchong.com/15948588/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15948588', + 'sites': [], + 'tags': ['中国手机摄影', '技术', '科学'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15948588/61839461/', + 'views': 454 + }, + { + 'author_id': '1417872', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': '今天拍摄效率高,晚上要给自己加鸡腿~~', + 'created_at': '2019-12-26 18:20:20', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '1SharePlus静物', + '1252PHOTO', + '我要上开屏', + '生活小确幸', + '绝不停止记录的2019', + '我要上“首页推荐位”', + '拍拍好吃的' + ], + 'excerpt': '今天拍摄效率高,晚上要给自己加鸡腿~~', + 'favorite_list_prefix': [], + 'favorites': 50, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 118933078, + 'img_id_str': '118933078', + 'title': '001', + 'user_id': 1417872, + 'width': 1800 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '2019年12月26日', + 'post_id': 61001930, + 'published_at': '2019-12-26 18:20:20', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '2018“伯奇杯”中国创意摄影展入围摄影师', + 'domain': '', + 'followers': 33249, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1417872_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'KURAKO雅岚', + 'site_id': '1417872', + 'type': 'user', + 'url': 'https://tuchong.com/1417872/', + 'verification_list': [ + { + 'verification_reason': '2018“伯奇杯”中国创意摄影展入围摄影师', + 'verification_type': 12 + } + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1417872', + 'sites': [], + 'tags': [ + '1SharePlus静物', + '1252PHOTO', + '我要上开屏', + '生活小确幸', + '绝不停止记录的2019', + '我要上“首页推荐位”' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1417872/61001930/', + 'views': 2187 + }, + { + 'author_id': '15112973', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': '', + 'created_at': '2019-12-19 09:53:22', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['我要上“首页推荐位”'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 29, + 'image_count': 6, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 737, + 'img_id': 501269233, + 'img_id_str': '501269233', + 'title': '001', + 'user_id': 15112973, + 'width': 533 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 53724163, + 'img_id_str': '53724163', + 'title': '001', + 'user_id': 15112973, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 737, + 'img_id': 467190906, + 'img_id_str': '467190906', + 'title': '001', + 'user_id': 15112973, + 'width': 533 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 129614698, + 'img_id_str': '129614698', + 'title': '001', + 'user_id': 15112973, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 737, + 'img_id': 515884111, + 'img_id_str': '515884111', + 'title': '001', + 'user_id': 15112973, + 'width': 533 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 620610379, + 'img_id_str': '620610379', + 'title': '001', + 'user_id': 15112973, + 'width': 1200 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '2019年12月19日', + 'post_id': 60680609, + 'published_at': '2019-12-19 09:53:22', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深美食摄影师', + 'domain': '', + 'followers': 819, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15112973_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '北京美食摄影师龙一', + 'site_id': '15112973', + 'type': 'user', + 'url': 'https://tuchong.com/15112973/', + 'verification_list': [ + {'verification_reason': '资深美食摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '15112973', + 'sites': [], + 'tags': ['我要上“首页推荐位”', '美味的', '早餐', '菜谱摄影', '美食', '食物'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15112973/60680609/', + 'views': 1783 + }, + { + 'author_id': '4811813', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': '你的模样', + 'created_at': '2019-12-19 19:09:06', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['分享神仙颜值'], + 'excerpt': '你的模样', + 'favorite_list_prefix': [], + 'favorites': 64, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1075, + 'img_id': 568180878, + 'img_id_str': '568180878', + 'title': '001', + 'user_id': 4811813, + 'width': 1613 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '2019年12月19日', + 'post_id': 60701066, + 'published_at': '2019-12-19 19:09:06', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 2089, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_4811813_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '超子Max', + 'site_id': '4811813', + 'type': 'user', + 'url': 'https://tuchong.com/4811813/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '4811813', + 'sites': [], + 'tags': ['分享神仙颜值', '比基尼泳装', '性感的', '美丽', '时尚', '女孩'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/4811813/60701066/', + 'views': 3351 + }, + { + 'author_id': '3028265', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 35, + 'content': 'An ode to the Black Forest.', + 'created_at': '2019-12-18 21:53:22', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '做人要低调,摄影也是', + '约拍馆摄影作品投稿', + '我要上“首页推荐位”', + '绝不停止记录的2019', + '高品人像摄影', + '人像爱好者', + '人像摄影集', + '暖色调人像', + '有温度的人像', + '人像写真摄影约拍' + ], + 'excerpt': 'An ode to the Black Forest.', + 'favorite_list_prefix': [], + 'favorites': 427, + 'image_count': 5, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2190, + 'img_id': 200262710, + 'img_id_str': '200262710', + 'title': '2610968', + 'user_id': 3028265, + 'width': 1549 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2306, + 'img_id': 243909661, + 'img_id_str': '243909661', + 'title': '2610967', + 'user_id': 3028265, + 'width': 1615 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2317, + 'img_id': 475644948, + 'img_id_str': '475644948', + 'title': '2610966', + 'user_id': 3028265, + 'width': 1605 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1350, + 'img_id': 236635167, + 'img_id_str': '236635167', + 'title': '2610951', + 'user_id': 3028265, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2287, + 'img_id': 149472242, + 'img_id_str': '149472242', + 'title': '2610946', + 'user_id': 3028265, + 'width': 1608 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '29', + 'passed_time': '2019年12月18日', + 'post_id': 60664715, + 'published_at': '2019-12-18 21:53:22', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 20, + 'site': { + 'description': '资深儿童摄影师', + 'domain': '', + 'followers': 3301, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3028265_5', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'CICIVisualStudio', + 'site_id': '3028265', + 'type': 'user', + 'url': 'https://tuchong.com/3028265/', + 'verification_list': [ + {'verification_reason': '资深儿童摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3028265', + 'sites': [], + 'tags': [ + '做人要低调,摄影也是', + '约拍馆摄影作品投稿', + '我要上“首页推荐位”', + '绝不停止记录的2019', + '高品人像摄影', + '人像爱好者' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3028265/60664715/', + 'views': 26644 + }, + { + 'author_id': '7547729', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '小窗风定曲肱眠,骨冷魂清梦易圆。 蝴蝶不飞花自落,海棠枝下月娟娟。', + 'created_at': '2019-12-18 18:03:26', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['成都人像爱好者', '成都约拍圈子', '古装摄影圈子', '古风摄影研习社', '周末好时光'], + 'excerpt': '小窗风定曲肱眠,骨冷魂清梦易圆。 蝴蝶不飞花自落,海棠枝下月娟娟。', + 'favorite_list_prefix': [], + 'favorites': 55, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3675, + 'img_id': 94683657, + 'img_id_str': '94683657', + 'title': '001', + 'user_id': 7547729, + 'width': 5512 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5461, + 'img_id': 358073837, + 'img_id_str': '358073837', + 'title': '001', + 'user_id': 7547729, + 'width': 3641 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 197117068, + 'img_id_str': '197117068', + 'title': '001', + 'user_id': 7547729, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 415352435, + 'img_id_str': '415352435', + 'title': '001', + 'user_id': 7547729, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3753, + 'img_id': 471777985, + 'img_id_str': '471777985', + 'title': '001', + 'user_id': 7547729, + 'width': 5630 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 433439594, + 'img_id_str': '433439594', + 'title': '001', + 'user_id': 7547729, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 393200542, + 'img_id_str': '393200542', + 'title': '001', + 'user_id': 7547729, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 196724326, + 'img_id_str': '196724326', + 'title': '001', + 'user_id': 7547729, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3322, + 'img_id': 395560533, + 'img_id_str': '395560533', + 'title': '001', + 'user_id': 7547729, + 'width': 4983 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3456, + 'img_id': 614188098, + 'img_id_str': '614188098', + 'title': '001', + 'user_id': 7547729, + 'width': 5184 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '2019年12月18日', + 'post_id': 60653137, + 'published_at': '2019-12-18 18:03:26', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 225, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_7547729_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '影青竹', + 'site_id': '7547729', + 'type': 'user', + 'url': 'https://tuchong.com/7547729/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '7547729', + 'sites': [], + 'tags': ['成都人像爱好者', '成都约拍圈子', '古装摄影圈子', '古风摄影研习社', '周末好时光', '夜景人像'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/7547729/60653137/', + 'views': 2497 + }, + { + 'author_id': '1807584', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 14, + 'content': '', + 'created_at': '2019-09-10 22:22:51', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['分享神仙颜值'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 305, + 'image_count': 20, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 376414690, + 'img_id_str': '376414690', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 45850557, + 'img_id_str': '45850557', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 653500743, + 'img_id_str': '653500743', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3222, + 'img_id': 424714551, + 'img_id_str': '424714551', + 'title': '', + 'user_id': 1807584, + 'width': 2176 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 447848916, + 'img_id_str': '447848916', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 180855163, + 'img_id_str': '180855163', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3166, + 'img_id': 410493653, + 'img_id_str': '410493653', + 'title': '', + 'user_id': 1807584, + 'width': 2176 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 508928402, + 'img_id_str': '508928402', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 400597006, + 'img_id_str': '400597006', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 326213832, + 'img_id_str': '326213832', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 383492477, + 'img_id_str': '383492477', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 622567997, + 'img_id_str': '622567997', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 241934344, + 'img_id_str': '241934344', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 122200063, + 'img_id_str': '122200063', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 81633881, + 'img_id_str': '81633881', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 283287648, + 'img_id_str': '283287648', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 343515014, + 'img_id_str': '343515014', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 213230066, + 'img_id_str': '213230066', + 'title': '', + 'user_id': 1807584, + 'width': 2238 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 134127631, + 'img_id_str': '134127631', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 563650898, + 'img_id_str': '563650898', + 'title': '', + 'user_id': 1807584, + 'width': 2244 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '13', + 'passed_time': '2019年09月10日', + 'post_id': 52220392, + 'published_at': '2019-09-10 22:22:51', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 5, + 'site': { + 'description': '独立摄影师,参与「夏日短歌——图虫·OPEN MUJI摄影俳句展」', + 'domain': 'huenjs.tuchong.com', + 'followers': 5458, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1807584_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'huenjs', + 'site_id': '1807584', + 'type': 'user', + 'url': 'https://huenjs.tuchong.com/', + 'verification_list': [ + { + 'verification_reason': '独立摄影师,参与「夏日短歌——图虫·OPEN MUJI摄影俳句展」', + 'verification_type': 12 + } + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1807584', + 'sites': [], + 'tags': ['分享神仙颜值', '人像', '黑白', '胶片', '写真'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://huenjs.tuchong.com/52220392/', + 'views': 18261 + }, + { + 'author_id': '15957053', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 5, + 'content': '', + 'created_at': '2020-01-19 15:00:38', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['街拍纪实手册'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 15, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3456, + 'img_id': 235783765, + 'img_id_str': '235783765', + 'title': '001', + 'user_id': 15957053, + 'width': 5184 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3447, + 'img_id': 323798105, + 'img_id_str': '323798105', + 'title': '001', + 'user_id': 15957053, + 'width': 5170 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '01月19日', + 'post_id': 61838797, + 'published_at': '2020-01-19 15:00:38', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 3, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15957053_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '謝十玖', + 'site_id': '15957053', + 'type': 'user', + 'url': 'https://tuchong.com/15957053/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15957053', + 'sites': [], + 'tags': ['街拍纪实手册', '街道'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15957053/61838797/', + 'views': 507 + }, + { + 'author_id': '15956647', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '去年夏天的果子🎈', + 'created_at': '2020-01-19 10:57:13', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['静物美学'], + 'excerpt': '去年夏天的果子🎈', + 'favorite_list_prefix': [], + 'favorites': 13, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 494716969, + 'img_id_str': '494716969', + 'title': '1164033', + 'user_id': 15956647, + 'width': 3000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月19日', + 'post_id': 61833802, + 'published_at': '2020-01-19 10:57:13', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 4, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15956647_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '州上洲', + 'site_id': '15956647', + 'type': 'user', + 'url': 'https://tuchong.com/15956647/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15956647', + 'sites': [], + 'tags': ['静物美学', '植物', '果实', '手机'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15956647/61833802/', + 'views': 607 + }, + { + 'author_id': '15942917', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 6, + 'content': '您的秋季女孩~', + 'created_at': '2020-01-18 14:15:53', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['有温度的人像', '我们都爱日系摄影', '分享神仙颜值', '拍女友才是正经事'], + 'excerpt': '您的秋季女孩~', + 'favorite_list_prefix': [], + 'favorites': 16, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5999, + 'img_id': 198559218, + 'img_id_str': '198559218', + 'title': '610790', + 'user_id': 15942917, + 'width': 3368 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5193, + 'img_id': 314230368, + 'img_id_str': '314230368', + 'title': '610772', + 'user_id': 15942917, + 'width': 3368 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5999, + 'img_id': 256231330, + 'img_id_str': '256231330', + 'title': '610792', + 'user_id': 15942917, + 'width': 3368 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2843, + 'img_id': 246204204, + 'img_id_str': '246204204', + 'title': '610773', + 'user_id': 15942917, + 'width': 1899 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3368, + 'img_id': 265734004, + 'img_id_str': '265734004', + 'title': '610774', + 'user_id': 15942917, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3368, + 'img_id': 152684305, + 'img_id_str': '152684305', + 'title': '610776', + 'user_id': 15942917, + 'width': 5341 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5999, + 'img_id': 76269269, + 'img_id_str': '76269269', + 'title': '610777', + 'user_id': 15942917, + 'width': 3368 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3368, + 'img_id': 324912920, + 'img_id_str': '324912920', + 'title': '610775', + 'user_id': 15942917, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2944, + 'img_id': 96978733, + 'img_id_str': '96978733', + 'title': '610778', + 'user_id': 15942917, + 'width': 1684 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3368, + 'img_id': 357811989, + 'img_id_str': '357811989', + 'title': '610819', + 'user_id': 15942917, + 'width': 6000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '01月18日', + 'post_id': 61811689, + 'published_at': '2020-01-18 14:15:53', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '微博 @没有摄影师\n扩列 Q3053379019', + 'domain': '', + 'followers': 8, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15942917_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '没有摄影师', + 'site_id': '15942917', + 'type': 'user', + 'url': 'https://tuchong.com/15942917/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15942917', + 'sites': [], + 'tags': ['有温度的人像', '我们都爱日系摄影', '分享神仙颜值', '拍女友才是正经事'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15942917/61811689/', + 'views': 624 + }, + { + 'author_id': '15663082', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '', + 'created_at': '2019-12-18 09:10:44', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '图虫旅行摄影圈子', + '街拍俱乐部圈子', + '人文纪实手册', + '带我看看,你的城市', + '人文天下', + '我们都是城市探险家', + '图虫城市建筑', + '街拍纪实手册' + ], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 38, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 339133721, + 'img_id_str': '339133721', + 'title': '193765', + 'user_id': 15663082, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 81773880, + 'img_id_str': '81773880', + 'title': '193772', + 'user_id': 15663082, + 'width': 2000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '2019年12月18日', + 'post_id': 60631146, + 'published_at': '2019-12-18 09:10:44', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '多多指教!', + 'domain': '', + 'followers': 81, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15663082_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Kanvz_Pat', + 'site_id': '15663082', + 'type': 'user', + 'url': 'https://tuchong.com/15663082/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15663082', + 'sites': [], + 'tags': [ + '图虫旅行摄影圈子', + '街拍俱乐部圈子', + '人文纪实手册', + '带我看看,你的城市', + '人文天下', + '我们都是城市探险家' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15663082/60631146/', + 'views': 1897 + }, + { + 'author_id': '2661037', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 62, + 'content': '', + 'created_at': '2019-10-12 01:18:19', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['新的一天,新的卡点'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1605, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4320, + 'img_id': 625128012, + 'img_id_str': '625128012', + 'title': '', + 'user_id': 2661037, + 'width': 2880 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4500, + 'img_id': 541961989, + 'img_id_str': '541961989', + 'title': '', + 'user_id': 2661037, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4500, + 'img_id': 333689303, + 'img_id_str': '333689303', + 'title': '', + 'user_id': 2661037, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4174, + 'img_id': 86225289, + 'img_id_str': '86225289', + 'title': '', + 'user_id': 2661037, + 'width': 2782 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 533115720, + 'img_id_str': '533115720', + 'title': '', + 'user_id': 2661037, + 'width': 4500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 493203723, + 'img_id_str': '493203723', + 'title': '', + 'user_id': 2661037, + 'width': 4500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4500, + 'img_id': 581284058, + 'img_id_str': '581284058', + 'title': '', + 'user_id': 2661037, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 640856745, + 'img_id_str': '640856745', + 'title': '', + 'user_id': 2661037, + 'width': 4500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4500, + 'img_id': 102871371, + 'img_id_str': '102871371', + 'title': '', + 'user_id': 2661037, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 86094868, + 'img_id_str': '86094868', + 'title': '', + 'user_id': 2661037, + 'width': 4500 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '60', + 'passed_time': '2019年10月12日', + 'post_id': 55629324, + 'published_at': '2019-10-12 01:18:19', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 46, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'tarcyjia.tuchong.com', + 'followers': 15341, + 'has_everphoto_note': false, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2661037_3', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'TarcyJia', + 'site_id': '2661037', + 'type': 'user', + 'url': 'https://tarcyjia.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2661037', + 'sites': [], + 'tags': ['新的一天,新的卡点', '人像', '少女', '胶片', '写真', '摄影'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tarcyjia.tuchong.com/55629324/', + 'views': 54236 + }, + { + 'author_id': '6948206', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '〔十二月〕', + 'created_at': '2019-12-16 19:46:35', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '〔十二月〕', + 'favorite_list_prefix': [], + 'favorites': 33, + 'image_count': 19, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4714, + 'img_id': 190104561, + 'img_id_str': '190104561', + 'title': '', + 'user_id': 6948206, + 'width': 3142 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3936, + 'img_id': 397526251, + 'img_id_str': '397526251', + 'title': '', + 'user_id': 6948206, + 'width': 2624 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 200197155, + 'img_id_str': '200197155', + 'title': '', + 'user_id': 6948206, + 'width': 3200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2624, + 'img_id': 202359384, + 'img_id_str': '202359384', + 'title': '', + 'user_id': 6948206, + 'width': 3936 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3936, + 'img_id': 497403354, + 'img_id_str': '497403354', + 'title': '', + 'user_id': 6948206, + 'width': 2624 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4500, + 'img_id': 89048201, + 'img_id_str': '89048201', + 'title': '', + 'user_id': 6948206, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 438485612, + 'img_id_str': '438485612', + 'title': '', + 'user_id': 6948206, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5679, + 'img_id': 359057308, + 'img_id_str': '359057308', + 'title': '', + 'user_id': 6948206, + 'width': 3786 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3936, + 'img_id': 376947623, + 'img_id_str': '376947623', + 'title': '', + 'user_id': 6948206, + 'width': 2624 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 521978952, + 'img_id_str': '521978952', + 'title': '', + 'user_id': 6948206, + 'width': 3200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 642499684, + 'img_id_str': '642499684', + 'title': '', + 'user_id': 6948206, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 56672956, + 'img_id_str': '56672956', + 'title': '', + 'user_id': 6948206, + 'width': 3200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 429573181, + 'img_id_str': '429573181', + 'title': '', + 'user_id': 6948206, + 'width': 3200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3936, + 'img_id': 425706356, + 'img_id_str': '425706356', + 'title': '', + 'user_id': 6948206, + 'width': 2624 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 79217589, + 'img_id_str': '79217589', + 'title': '', + 'user_id': 6948206, + 'width': 3200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 364168161, + 'img_id_str': '364168161', + 'title': '', + 'user_id': 6948206, + 'width': 3200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5100, + 'img_id': 432194737, + 'img_id_str': '432194737', + 'title': '', + 'user_id': 6948206, + 'width': 3400 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 562021417, + 'img_id_str': '562021417', + 'title': '', + 'user_id': 6948206, + 'width': 3200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2269, + 'img_id': 394249240, + 'img_id_str': '394249240', + 'title': '', + 'user_id': 6948206, + 'width': 3402 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '2019年12月16日', + 'post_id': 60555144, + 'published_at': '2019-12-16 19:46:35', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 6983, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_6948206_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '洛桑w伊梓', + 'site_id': '6948206', + 'type': 'user', + 'url': 'https://tuchong.com/6948206/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '6948206', + 'sites': [], + 'tags': ['日系', '男生写真', '写真', '人像', '色彩'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/6948206/60555144/', + 'views': 2734 + }, + { + 'author_id': '3738183', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 63, + 'content': '', + 'created_at': '2019-10-15 20:26:12', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['高颜值女神聚集地'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1474, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5792, + 'img_id': 113554471, + 'img_id_str': '113554471', + 'title': '785977', + 'user_id': 3738183, + 'width': 8688 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2428, + 'img_id': 652915682, + 'img_id_str': '652915682', + 'title': '786055', + 'user_id': 3738183, + 'width': 3500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 124367844, + 'img_id_str': '124367844', + 'title': '785976', + 'user_id': 3738183, + 'width': 5373 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5250, + 'img_id': 154579753, + 'img_id_str': '154579753', + 'title': '785975', + 'user_id': 3738183, + 'width': 3500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 214020918, + 'img_id_str': '214020918', + 'title': '785974', + 'user_id': 3738183, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 367703228, + 'img_id_str': '367703228', + 'title': '785973', + 'user_id': 3738183, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 601273027, + 'img_id_str': '601273027', + 'title': '785587', + 'user_id': 3738183, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 178893736, + 'img_id_str': '178893736', + 'title': '785583', + 'user_id': 3738183, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5250, + 'img_id': 68989749, + 'img_id_str': '68989749', + 'title': '785585', + 'user_id': 3738183, + 'width': 3500 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '59', + 'passed_time': '2019年10月15日', + 'post_id': 55988727, + 'published_at': '2019-10-15 20:26:12', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 44, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 15518, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3738183_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '钟月月', + 'site_id': '3738183', + 'type': 'user', + 'url': 'https://tuchong.com/3738183/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3738183', + 'sites': [], + 'tags': ['高颜值女神聚集地', '人像', '写真', '唯美'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3738183/55988727/', + 'views': 47522 + }, + { + 'author_id': '5695193', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 5, + 'content': '草莓?季', + 'created_at': '2020-01-05 14:31:49', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['妍色同学会', '拍拍好吃的', '静物美学'], + 'excerpt': '草莓?季', + 'favorite_list_prefix': [], + 'favorites': 79, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6001, + 'img_id': 331204044, + 'img_id_str': '331204044', + 'title': '001', + 'user_id': 5695193, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6001, + 'img_id': 385336846, + 'img_id_str': '385336846', + 'title': '001', + 'user_id': 5695193, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6001, + 'img_id': 367511365, + 'img_id_str': '367511365', + 'title': '001', + 'user_id': 5695193, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6001, + 'img_id': 141215220, + 'img_id_str': '141215220', + 'title': '001', + 'user_id': 5695193, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6001, + 'img_id': 478921936, + 'img_id_str': '478921936', + 'title': '001', + 'user_id': 5695193, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6001, + 'img_id': 601802219, + 'img_id_str': '601802219', + 'title': '001', + 'user_id': 5695193, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 519816775, + 'img_id_str': '519816775', + 'title': '001', + 'user_id': 5695193, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6001, + 'img_id': 346080895, + 'img_id_str': '346080895', + 'title': '001', + 'user_id': 5695193, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6001, + 'img_id': 646366815, + 'img_id_str': '646366815', + 'title': '001', + 'user_id': 5695193, + 'width': 4000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '5', + 'passed_time': '01月05日', + 'post_id': 61418795, + 'published_at': '2020-01-05 14:31:49', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 7, + 'site': { + 'description': '资深静物摄影师', + 'domain': '', + 'followers': 2696, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_5695193_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '小懒猫yiyi', + 'site_id': '5695193', + 'type': 'user', + 'url': 'https://tuchong.com/5695193/', + 'verification_list': [ + {'verification_reason': '资深静物摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '5695193', + 'sites': [], + 'tags': ['妍色同学会', '拍拍好吃的', '静物美学', '草莓', '静物美食摄影'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/5695193/61418795/', + 'views': 2254 + }, + { + 'author_id': '15771562', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '大大大床房', + 'created_at': '2019-12-28 09:10:52', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['微缩摄影研究所', '中国创意摄影', '取景器里的小美好'], + 'excerpt': '大大大床房', + 'favorite_list_prefix': [], + 'favorites': 46, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 914, + 'img_id': 379767001, + 'img_id_str': '379767001', + 'title': '001', + 'user_id': 15771562, + 'width': 914 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '4', + 'passed_time': '2019年12月28日', + 'post_id': 61079247, + 'published_at': '2019-12-28 09:10:52', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深创意摄影师', + 'domain': '', + 'followers': 474, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15771562_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '感青影像', + 'site_id': '15771562', + 'type': 'user', + 'url': 'https://tuchong.com/15771562/', + 'verification_list': [ + {'verification_reason': '资深创意摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '15771562', + 'sites': [], + 'tags': ['微缩摄影研究所', '中国创意摄影', '取景器里的小美好', '微型创意摄影'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15771562/61079247/', + 'views': 2230 + }, + { + 'author_id': '15791930', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '寺庙 青红色调', + 'created_at': '2019-12-25 20:15:22', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['带我看看,你的城市', '福建摄影圈', '让摄影穿破次元壁'], + 'excerpt': '寺庙 青红色调', + 'favorite_list_prefix': [], + 'favorites': 34, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5477, + 'img_id': 474989837, + 'img_id_str': '474989837', + 'title': '537552', + 'user_id': 15791930, + 'width': 3651 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5477, + 'img_id': 542098582, + 'img_id_str': '542098582', + 'title': '537553', + 'user_id': 15791930, + 'width': 3651 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '2019年12月25日', + 'post_id': 60971020, + 'published_at': '2019-12-25 20:15:22', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 15, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15791930_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'DoubleOreo', + 'site_id': '15791930', + 'type': 'user', + 'url': 'https://tuchong.com/15791930/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15791930', + 'sites': [], + 'tags': ['带我看看,你的城市', '福建摄影圈', '让摄影穿破次元壁', '寺庙', '建筑', '风光'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15791930/60971020/', + 'views': 1517 + }, + { + 'author_id': '1790780', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 47, + 'content': '蓝色\n\n摄影:@萌琦琦M77', + 'created_at': '2019-08-16 17:21:23', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '蓝色\n\n摄影:@萌琦琦M77', + 'favorite_list_prefix': [], + 'favorites': 1087, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2999, + 'img_id': 66031546, + 'img_id_str': '66031546', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 360419821, + 'img_id_str': '360419821', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 480219210, + 'img_id_str': '480219210', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 262640377, + 'img_id_str': '262640377', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 70095688, + 'img_id_str': '70095688', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 565285304, + 'img_id_str': '565285304', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 96702726, + 'img_id_str': '96702726', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 374968500, + 'img_id_str': '374968500', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 570790260, + 'img_id_str': '570790260', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '41', + 'passed_time': '2019年08月16日', + 'post_id': 49432246, + 'published_at': '2019-08-16 17:21:23', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 16, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 4991, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1790780_7', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '萌琦琦M77', + 'site_id': '1790780', + 'type': 'user', + 'url': 'https://tuchong.com/1790780/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1790780', + 'sites': [], + 'tags': ['少女写真', '美少女', '日系小清新', '日系写真'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1790780/49432246/', + 'views': 49887 + }, + { + 'author_id': '1303010', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 53, + 'content': 'cp25场照', + 'created_at': '2019-12-25 16:17:31', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['Cosplay摄影集中地'], + 'excerpt': 'cp25场照', + 'favorite_list_prefix': [], + 'favorites': 914, + 'image_count': 6, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 81904549, + 'img_id_str': '81904549', + 'title': '913651', + 'user_id': 1303010, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 212452816, + 'img_id_str': '212452816', + 'title': '913650', + 'user_id': 1303010, + 'width': 1066 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 624280683, + 'img_id_str': '624280683', + 'title': '913649', + 'user_id': 1303010, + 'width': 1066 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1080, + 'img_id': 478201230, + 'img_id_str': '478201230', + 'title': '913648', + 'user_id': 1303010, + 'width': 1620 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 337233337, + 'img_id_str': '337233337', + 'title': '913647', + 'user_id': 1303010, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1066, + 'img_id': 45794309, + 'img_id_str': '45794309', + 'title': '913646', + 'user_id': 1303010, + 'width': 1895 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '46', + 'passed_time': '2019年12月25日', + 'post_id': 60962339, + 'published_at': '2019-12-25 16:17:31', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 53, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': 'devilkman.tuchong.com', + 'followers': 4653, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1303010_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '咕哒囧子', + 'site_id': '1303010', + 'type': 'user', + 'url': 'https://devilkman.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1303010', + 'sites': [], + 'tags': ['Cosplay摄影集中地', 'Cosplay'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://devilkman.tuchong.com/60962339/', + 'views': 44224 + }, + { + 'author_id': '15879549', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 12, + 'content': '风雪夜归人', + 'created_at': '2020-01-15 21:08:45', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '风光人文地理', + '我们都在孤独星球', + '晒晒旅行打卡照', + '图虫旅行摄影圈子', + '一个风光摄影圈', + '光影者的风光摄影', + '中国地理摄影', + '风光摄影集', + '极致风光摄影', + '风光摄影圈', + '闪迪旅拍大赛' + ], + 'excerpt': '风雪夜归人', + 'favorite_list_prefix': [], + 'favorites': 85, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5504, + 'img_id': 449561976, + 'img_id_str': '449561976', + 'title': '001', + 'user_id': 15879549, + 'width': 8256 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '10', + 'passed_time': '01月15日', + 'post_id': 61739255, + 'published_at': '2020-01-15 21:08:45', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 6, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 417, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15879549_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Greatwangjia', + 'site_id': '15879549', + 'type': 'user', + 'url': 'https://tuchong.com/15879549/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '15879549', + 'sites': [], + 'tags': [ + '风光人文地理', + '我们都在孤独星球', + '晒晒旅行打卡照', + '图虫旅行摄影圈子', + '一个风光摄影圈', + '光影者的风光摄影' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15879549/61739255/', + 'views': 1337 + }, + { + 'author_id': '3802000', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 66, + 'content': null, + 'created_at': '2019-12-31 20:48:54', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': + '图虫编者按:2019年已经过去,在这一年里,大家又拍摄了哪些喜欢的照片?自己在这一年有什么收获。欢迎大家都来总结一下自己一年的收获,跟大家来分享一下自己的摄影故事。分享自己的摄影故事,请打上 #年度总结# 标签,我们会筛选有趣的内容跟大家分享。我从十八还是十九岁出来社会打拼,从一无所有,发展到身无分文,再从身无分文到负债累累!所幸今年年末明确了目标,学会了思考.感谢感恩身边的所遇到的每个人。2019年有一...', + 'favorite_list_prefix': [], + 'favorites': 123, + 'image_count': 0, + 'images': [], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '28', + 'passed_time': '2019年12月31日', + 'post_id': 61243712, + 'published_at': '2019-12-31 20:48:54', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 26, + 'site': { + 'description': '擅治愈、清新风格.VB.@小龙在拍照', + 'domain': '', + 'followers': 323, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3802000_10', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '小龙在拍照', + 'site_id': '3802000', + 'type': 'user', + 'url': 'https://tuchong.com/3802000/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '3802000', + 'sites': [], + 'tags': ['松果计划', '摄影心得', '摄影教程', '年度总结'], + 'title': '2019年终总结', + 'title_image': { + 'width': 600, + 'height': 269, + 'url': 'https://tuchong.pstatp.com/3802000/l/475514660.jpg' + }, + 'type': 'text', + 'update': false, + 'url': 'https://tuchong.com/3802000/t/61243712/', + 'views': 5106 + }, + { + 'author_id': '4586948', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '下课', + 'created_at': '2019-12-19 07:14:05', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['暖色调人像', '写真人像摄影', '高颜值女神聚集地', '人像爱好者', '我们都爱日系摄影'], + 'excerpt': '下课', + 'favorite_list_prefix': [], + 'favorites': 45, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 160285367, + 'img_id_str': '160285367', + 'title': '001', + 'user_id': 4586948, + 'width': 2678 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '2019年12月19日', + 'post_id': 60676388, + 'published_at': '2019-12-19 07:14:05', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 794, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_4586948_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '慧子photo', + 'site_id': '4586948', + 'type': 'user', + 'url': 'https://tuchong.com/4586948/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '4586948', + 'sites': [], + 'tags': ['暖色调人像', '写真人像摄影', '高颜值女神聚集地', '人像爱好者', '我们都爱日系摄影', '在室内'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/4586948/60676388/', + 'views': 2225 + }, + { + 'author_id': '1004274', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '', + 'created_at': '2019-12-23 13:31:09', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 23, + 'image_count': 6, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 122602313, + 'img_id_str': '122602313', + 'title': '', + 'user_id': 1004274, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 434685176, + 'img_id_str': '434685176', + 'title': '', + 'user_id': 1004274, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 502973502, + 'img_id_str': '502973502', + 'title': '', + 'user_id': 1004274, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6521, + 'img_id': 276021779, + 'img_id_str': '276021779', + 'title': '', + 'user_id': 1004274, + 'width': 3668 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 489866516, + 'img_id_str': '489866516', + 'title': '', + 'user_id': 1004274, + 'width': 3780 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 600360137, + 'img_id_str': '600360137', + 'title': '', + 'user_id': 1004274, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '4', + 'passed_time': '2019年12月23日', + 'post_id': 60871698, + 'published_at': '2019-12-23 13:31:09', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': 'soushou.tuchong.com', + 'followers': 6347, + 'has_everphoto_note': false, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1004274_25', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'soushou', + 'site_id': '1004274', + 'type': 'user', + 'url': 'https://soushou.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1004274', + 'sites': [], + 'tags': ['色彩', '人像', '佳能', '美女', '少女', '女孩'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://soushou.tuchong.com/60871698/', + 'views': 1606 + }, + { + 'author_id': '45644', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 10, + 'content': '', + 'created_at': '2019-12-29 13:43:47', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '我要上“首页推荐位”', + '糖水人像小组', + '分享神仙颜值', + '我要上开屏', + '上海外拍活动', + '美女人像群', + '人像爱好者', + '高颜值女神聚集地', + '上海人像圈子' + ], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 183, + 'image_count': 27, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 631358799, + 'img_id_str': '631358799', + 'title': '1389814', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 246072738, + 'img_id_str': '246072738', + 'title': '1389813', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 189318518, + 'img_id_str': '189318518', + 'title': '1389819', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 236242766, + 'img_id_str': '236242766', + 'title': '1389816', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 608486768, + 'img_id_str': '608486768', + 'title': '1389821', + 'user_id': 45644, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 482854638, + 'img_id_str': '482854638', + 'title': '1389823', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 247317339, + 'img_id_str': '247317339', + 'title': '1389822', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 384157460, + 'img_id_str': '384157460', + 'title': '1389817', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 418039485, + 'img_id_str': '418039485', + 'title': '1389824', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 399951419, + 'img_id_str': '399951419', + 'title': '1389825', + 'user_id': 45644, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 301713344, + 'img_id_str': '301713344', + 'title': '1389827', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 421906010, + 'img_id_str': '421906010', + 'title': '1389826', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 116901835, + 'img_id_str': '116901835', + 'title': '1389832', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 458213268, + 'img_id_str': '458213268', + 'title': '1389828', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 431670789, + 'img_id_str': '431670789', + 'title': '1389829', + 'user_id': 45644, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 628933923, + 'img_id_str': '628933923', + 'title': '1389830', + 'user_id': 45644, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 501400746, + 'img_id_str': '501400746', + 'title': '1389840', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 648594903, + 'img_id_str': '648594903', + 'title': '1389846', + 'user_id': 45644, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 171295910, + 'img_id_str': '171295910', + 'title': '1389833', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 352634391, + 'img_id_str': '352634391', + 'title': '1389835', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 168740185, + 'img_id_str': '168740185', + 'title': '1389842', + 'user_id': 45644, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 287753714, + 'img_id_str': '287753714', + 'title': '1389844', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 204130146, + 'img_id_str': '204130146', + 'title': '1389845', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 224577226, + 'img_id_str': '224577226', + 'title': '1389841', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 474793423, + 'img_id_str': '474793423', + 'title': '1389838', + 'user_id': 45644, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 592758406, + 'img_id_str': '592758406', + 'title': '1389831', + 'user_id': 45644, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 307676791, + 'img_id_str': '307676791', + 'title': '1389836', + 'user_id': 45644, + 'width': 3000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '9', + 'passed_time': '2019年12月29日', + 'post_id': 61137324, + 'published_at': '2019-12-29 13:43:47', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 6, + 'site': { + 'description': '资深人像摄影师', + 'domain': '95hugo.tuchong.com', + 'followers': 880, + 'has_everphoto_note': false, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_45644_7', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '九月的雨果', + 'site_id': '45644', + 'type': 'user', + 'url': 'https://95hugo.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '45644', + 'sites': [], + 'tags': ['我要上“首页推荐位”', '糖水人像小组', '分享神仙颜值', '我要上开屏', '上海外拍活动', '美女人像群'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://95hugo.tuchong.com/61137324/', + 'views': 7627 + }, + { + 'author_id': '2981258', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '日出申华', + 'created_at': '2020-01-09 23:38:51', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '华理视觉摄影社', + '城市·夜晚', + '极致风光摄影', + '我要上开屏', + '风光摄影集', + '图虫城市建筑', + '带我看看,你的城市', + '华东理工大学摄影圈', + '绝不停止记录的2019', + '我要上“首页推荐位”' + ], + 'excerpt': '日出申华', + 'favorite_list_prefix': [], + 'favorites': 39, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3873, + 'img_id': 324716207, + 'img_id_str': '324716207', + 'title': '001', + 'user_id': 2981258, + 'width': 6048 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月09日', + 'post_id': 61561630, + 'published_at': '2020-01-09 23:38:51', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 7097, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2981258_5', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'MaxWell_Z', + 'site_id': '2981258', + 'type': 'user', + 'url': 'https://tuchong.com/2981258/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2981258', + 'sites': [], + 'tags': ['华理视觉摄影社', '城市·夜晚', '极致风光摄影', '我要上开屏', '风光摄影集', '图虫城市建筑'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2981258/61561630/', + 'views': 916 + }, + { + 'author_id': '390179', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '小森林的秋冬暖阳\n\n从有你的清晨醒来\n生活变成了电影\n岛屿变成星星\n在海里放光明\n\n场地@加糖写真馆', + 'created_at': '2019-12-27 13:21:57', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['写真人像摄影', '人像摄影集', '约拍馆摄影作品投稿'], + 'excerpt': '小森林的秋冬暖阳\n\n从有你的清晨醒来\n生活变成了电影\n岛屿变成星星\n在海里放光明\n\n场地@加糖写真馆', + 'favorite_list_prefix': [], + 'favorites': 58, + 'image_count': 26, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 363513300, + 'img_id_str': '363513300', + 'title': '001', + 'user_id': 390179, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 566019556, + 'img_id_str': '566019556', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 469944079, + 'img_id_str': '469944079', + 'title': '001', + 'user_id': 390179, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 313639857, + 'img_id_str': '313639857', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3349, + 'img_id': 134727105, + 'img_id_str': '134727105', + 'title': '001', + 'user_id': 390179, + 'width': 2263 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 251380844, + 'img_id_str': '251380844', + 'title': '001', + 'user_id': 390179, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 275301287, + 'img_id_str': '275301287', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 343393440, + 'img_id_str': '343393440', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 306824877, + 'img_id_str': '306824877', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 582731111, + 'img_id_str': '582731111', + 'title': '001', + 'user_id': 390179, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 260490482, + 'img_id_str': '260490482', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 547734810, + 'img_id_str': '547734810', + 'title': '001', + 'user_id': 390179, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 200329244, + 'img_id_str': '200329244', + 'title': '001', + 'user_id': 390179, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 419480634, + 'img_id_str': '419480634', + 'title': '001', + 'user_id': 390179, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 261211410, + 'img_id_str': '261211410', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 311477341, + 'img_id_str': '311477341', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 90555731, + 'img_id_str': '90555731', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 354403800, + 'img_id_str': '354403800', + 'title': '001', + 'user_id': 390179, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 600032276, + 'img_id_str': '600032276', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 252102098, + 'img_id_str': '252102098', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 572048731, + 'img_id_str': '572048731', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 653575686, + 'img_id_str': '653575686', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 407553424, + 'img_id_str': '407553424', + 'title': '001', + 'user_id': 390179, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 277792391, + 'img_id_str': '277792391', + 'title': '001', + 'user_id': 390179, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 353486069, + 'img_id_str': '353486069', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 458671708, + 'img_id_str': '458671708', + 'title': '001', + 'user_id': 390179, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '4', + 'passed_time': '2019年12月27日', + 'post_id': 61026480, + 'published_at': '2019-12-27 13:21:57', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 115, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_390179_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '兔丁小姐', + 'site_id': '390179', + 'type': 'user', + 'url': 'https://tuchong.com/390179/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '390179', + 'sites': [], + 'tags': ['写真人像摄影', '人像摄影集', '约拍馆摄影作品投稿', '人像', '日系', '少女写真'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/390179/61026480/', + 'views': 2373 + }, + { + 'author_id': '300897', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 5, + 'content': '', + 'created_at': '2019-12-30 17:39:15', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['2019你最满意的照片', '闪迪旅拍大赛', '悦拍评片会', '飞图摄影学院评片会', '2020适马睛典'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 67, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 542492787, + 'img_id_str': '542492787', + 'title': '', + 'user_id': 300897, + 'width': 3000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '5', + 'passed_time': '2019年12月30日', + 'post_id': 61189888, + 'published_at': '2019-12-30 17:39:15', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 4, + 'site': { + 'description': '资深旅行摄影师', + 'domain': 'hockingcao.tuchong.com', + 'followers': 4195, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_300897_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Hockingcao', + 'site_id': '300897', + 'type': 'user', + 'url': 'https://hockingcao.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深旅行摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '300897', + 'sites': [], + 'tags': ['2019你最满意的照片', '闪迪旅拍大赛', '悦拍评片会', '飞图摄影学院评片会', '2020适马睛典', '风光'], + 'title': '圣托里尼晚霞', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://hockingcao.tuchong.com/61189888/', + 'views': 1960 + }, + { + 'author_id': '1738925', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 5, + 'content': '这组定制客片,从筹备到拍摄,只用了两天的时间,是我拍摄定制写真筹备最快的一次。', + 'created_at': '2019-12-25 13:28:32', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['2019你最满意的照片'], + 'excerpt': '这组定制客片,从筹备到拍摄,只用了两天的时间,是我拍摄定制写真筹备最快的一次。', + 'favorite_list_prefix': [], + 'favorites': 25, + 'image_count': 13, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1364, + 'img_id': 71419233, + 'img_id_str': '71419233', + 'title': '', + 'user_id': 1738925, + 'width': 2047 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1364, + 'img_id': 573424948, + 'img_id_str': '573424948', + 'title': '', + 'user_id': 1738925, + 'width': 2047 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1364, + 'img_id': 96585025, + 'img_id_str': '96585025', + 'title': '', + 'user_id': 1738925, + 'width': 2045 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1360, + 'img_id': 576242950, + 'img_id_str': '576242950', + 'title': '', + 'user_id': 1738925, + 'width': 2045 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2206, + 'img_id': 461161887, + 'img_id_str': '461161887', + 'title': '', + 'user_id': 1738925, + 'width': 1471 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2206, + 'img_id': 517064260, + 'img_id_str': '517064260', + 'title': '', + 'user_id': 1738925, + 'width': 1471 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2206, + 'img_id': 411354221, + 'img_id_str': '411354221', + 'title': '', + 'user_id': 1738925, + 'width': 1471 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2206, + 'img_id': 556844204, + 'img_id_str': '556844204', + 'title': '', + 'user_id': 1738925, + 'width': 1471 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2206, + 'img_id': 69518344, + 'img_id_str': '69518344', + 'title': '', + 'user_id': 1738925, + 'width': 1471 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2206, + 'img_id': 286311726, + 'img_id_str': '286311726', + 'title': '', + 'user_id': 1738925, + 'width': 1471 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2206, + 'img_id': 558810792, + 'img_id_str': '558810792', + 'title': '', + 'user_id': 1738925, + 'width': 1471 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2206, + 'img_id': 69649370, + 'img_id_str': '69649370', + 'title': '', + 'user_id': 1738925, + 'width': 1471 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2206, + 'img_id': 226280942, + 'img_id_str': '226280942', + 'title': '', + 'user_id': 1738925, + 'width': 1471 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '2019年12月25日', + 'post_id': 60956923, + 'published_at': '2019-12-25 13:28:32', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '相互关注,相互关照。', + 'domain': '', + 'followers': 101, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1738925_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '李佩珊', + 'site_id': '1738925', + 'type': 'user', + 'url': 'https://tuchong.com/1738925/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '1738925', + 'sites': [], + 'tags': ['2019你最满意的照片', '2019-所爱', '色彩', '人像', '美女', '女孩'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1738925/60956923/', + 'views': 1655 + }, + { + 'author_id': '1609854', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '', + 'created_at': '2020-01-07 15:48:51', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['2020适马睛典'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 32, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6336, + 'img_id': 572770058, + 'img_id_str': '572770058', + 'title': '001', + 'user_id': 1609854, + 'width': 9504 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '01月07日', + 'post_id': 61487318, + 'published_at': '2020-01-07 15:48:51', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深旅行摄影师', + 'domain': 'carrotjam.tuchong.com', + 'followers': 12084, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1609854_8', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '胡萝卜果酱', + 'site_id': '1609854', + 'type': 'user', + 'url': 'https://carrotjam.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深旅行摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1609854', + 'sites': [], + 'tags': ['2020适马睛典', '睛典风光组', '山', '晚上', '天空', '色彩'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://carrotjam.tuchong.com/61487318/', + 'views': 927 + }, + { + 'author_id': '1534403', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 5, + 'content': '冬', + 'created_at': '2019-12-18 19:17:29', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '冬', + 'favorite_list_prefix': [], + 'favorites': 104, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 604291631, + 'img_id_str': '604291631', + 'title': '4287357', + 'user_id': 1534403, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '5', + 'passed_time': '2019年12月18日', + 'post_id': 60656433, + 'published_at': '2019-12-18 19:17:29', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 2, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 7389, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1534403_27', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'Akasa空影照见', + 'site_id': '1534403', + 'type': 'user', + 'url': 'https://tuchong.com/1534403/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1534403', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1534403/60656433/', + 'views': 4094 + }, + { + 'author_id': '2874671', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '坐着爷爷的摩托去巴扎', + 'created_at': '2020-01-16 12:10:23', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '图虫旅行摄影圈子', + '街事圈子', + '我爱街拍摄影小组圈子', + '街头拍客', + '人文纪实手册', + '街头人物圈子', + '街拍纪实手册', + '人文天下', + '街拍俱乐部圈子', + '有温度的人像' + ], + 'excerpt': '坐着爷爷的摩托去巴扎', + 'favorite_list_prefix': [], + 'favorites': 21, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2729, + 'img_id': 483640919, + 'img_id_str': '483640919', + 'title': '001', + 'user_id': 2874671, + 'width': 4096 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '01月16日', + 'post_id': 61754269, + 'published_at': '2020-01-16 12:10:23', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '不是摄影师', + 'domain': '', + 'followers': 25, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2874671_6', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '木司晨', + 'site_id': '2874671', + 'type': 'user', + 'url': 'https://tuchong.com/2874671/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '2874671', + 'sites': [], + 'tags': ['图虫旅行摄影圈子', '街事圈子', '我爱街拍摄影小组圈子', '街头拍客', '人文纪实手册', '街头人物圈子'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2874671/61754269/', + 'views': 996 + }, + { + 'author_id': '2693179', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 74, + 'content': null, + 'created_at': '2019-12-21 14:32:27', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': + '陈 萧 伊出生于中国四川,2014年获得伦敦艺术大学纯艺术摄影硕士学位,目前工作生活于成都。她的作品曾获得中国第七届三影堂摄影奖,入选福布斯2017“30 UNDER 30(Art)”亚洲榜单,并参与诸多国内外展览。她的作品基于摄影,但并不局限于具体的媒介。通过生产图像的方式,她关注生命体的微妙感知,通过不断挑战既定的逻辑、感知与想象,来探讨存在本身的问题。她一直感怀并向往着远古时代,她觉得古人的生活里有太多的智慧,物...', + 'favorite_list_prefix': [], + 'favorites': 138, + 'image_count': 0, + 'images': [], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '60', + 'passed_time': '2019年12月21日', + 'post_id': 60778056, + 'published_at': '2019-12-21 14:32:27', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 61, + 'site': { + 'description': '图虫品牌项目,包括演讲、展览、文创及出版', + 'domain': 'opensee.tuchong.com', + 'followers': 128878, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2693179_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'OpenSee', + 'site_id': '2693179', + 'type': 'user', + 'url': 'https://opensee.tuchong.com/', + 'verification_list': [ + { + 'verification_reason': '图虫品牌项目,包括演讲、展览、文创及出版', + 'verification_type': 11 + } + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2693179', + 'sites': [], + 'tags': [], + 'title': '凝视冰川、月亮和岩石,陈萧伊用摄影探寻世界隐秘的本原', + 'title_image': { + 'width': 600, + 'height': 253, + 'url': 'https://tuchong.pstatp.com/2693179/l/576178325.jpg' + }, + 'type': 'text', + 'update': false, + 'url': 'https://opensee.tuchong.com/t/60778056/', + 'views': 11595 + }, + { + 'author_id': '4091385', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': 'The simple portraits\nModel: Zenia', + 'created_at': '2020-01-16 16:40:07', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['糖水人像小组', '有温度的人像', '暖色调人像'], + 'excerpt': 'The simple portraits\nModel: Zenia', + 'favorite_list_prefix': [], + 'favorites': 49, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 418564081, + 'img_id_str': '418564081', + 'title': '001', + 'user_id': 4091385, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 620939231, + 'img_id_str': '620939231', + 'title': '001', + 'user_id': 4091385, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 269076266, + 'img_id_str': '269076266', + 'title': '001', + 'user_id': 4091385, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 388482651, + 'img_id_str': '388482651', + 'title': '001', + 'user_id': 4091385, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5523, + 'img_id': 491505331, + 'img_id_str': '491505331', + 'title': '001', + 'user_id': 4091385, + 'width': 3682 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6610, + 'img_id': 538822304, + 'img_id_str': '538822304', + 'title': '001', + 'user_id': 4091385, + 'width': 4407 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 462669858, + 'img_id_str': '462669858', + 'title': '001', + 'user_id': 4091385, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4160, + 'img_id': 222152520, + 'img_id_str': '222152520', + 'title': '001', + 'user_id': 4091385, + 'width': 6240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 90688011, + 'img_id_str': '90688011', + 'title': '001', + 'user_id': 4091385, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5056, + 'img_id': 138659221, + 'img_id_str': '138659221', + 'title': '001', + 'user_id': 4091385, + 'width': 3371 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月16日', + 'post_id': 61759996, + 'published_at': '2020-01-16 16:40:07', + 'recommend': true, + 'recom_type': '', + 'rewardable': false, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 4, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 1434, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_4091385_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '開米小關', + 'site_id': '4091385', + 'type': 'user', + 'url': 'https://tuchong.com/4091385/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '4091385', + 'sites': [], + 'tags': ['糖水人像小组', '有温度的人像', '暖色调人像', '人像', '肖像', '色彩'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/4091385/61759996/', + 'views': 1992 + }, + { + 'author_id': '8110595', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '碧蓝幻想-美黛拉', + 'created_at': '2019-12-24 21:09:09', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['Cosplay摄影集中地'], + 'excerpt': '碧蓝幻想-美黛拉', + 'favorite_list_prefix': [], + 'favorites': 87, + 'image_count': 17, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 250791302, + 'img_id_str': '250791302', + 'title': '001', + 'user_id': 8110595, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 239715567, + 'img_id_str': '239715567', + 'title': '001', + 'user_id': 8110595, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 389858914, + 'img_id_str': '389858914', + 'title': '001', + 'user_id': 8110595, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 594724153, + 'img_id_str': '594724153', + 'title': '001', + 'user_id': 8110595, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 160875916, + 'img_id_str': '160875916', + 'title': '001', + 'user_id': 8110595, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 446415831, + 'img_id_str': '446415831', + 'title': '001', + 'user_id': 8110595, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 362988627, + 'img_id_str': '362988627', + 'title': '001', + 'user_id': 8110595, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 116769853, + 'img_id_str': '116769853', + 'title': '001', + 'user_id': 8110595, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 394315233, + 'img_id_str': '394315233', + 'title': '001', + 'user_id': 8110595, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5689, + 'img_id': 540394286, + 'img_id_str': '540394286', + 'title': '001', + 'user_id': 8110595, + 'width': 3793 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 650102110, + 'img_id_str': '650102110', + 'title': '001', + 'user_id': 8110595, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 568248103, + 'img_id_str': '568248103', + 'title': '001', + 'user_id': 8110595, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 39765074, + 'img_id_str': '39765074', + 'title': '001', + 'user_id': 8110595, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 148882650, + 'img_id_str': '148882650', + 'title': '001', + 'user_id': 8110595, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 331793578, + 'img_id_str': '331793578', + 'title': '001', + 'user_id': 8110595, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 344377060, + 'img_id_str': '344377060', + 'title': '001', + 'user_id': 8110595, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 198035263, + 'img_id_str': '198035263', + 'title': '001', + 'user_id': 8110595, + 'width': 6000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '4', + 'passed_time': '2019年12月24日', + 'post_id': 60930883, + 'published_at': '2019-12-24 21:09:09', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 2, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 5741, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_8110595_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '小穗穗穗sui', + 'site_id': '8110595', + 'type': 'user', + 'url': 'https://tuchong.com/8110595/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '8110595', + 'sites': [], + 'tags': ['Cosplay摄影集中地', '圣诞', 'Cosplay', '人像'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/8110595/60930883/', + 'views': 4452 + }, + { + 'author_id': '1517263', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '「汉口杂片集」冬季的日夜雨晴', + 'created_at': '2020-01-17 10:32:01', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '武汉城记', + '城市·夜晚', + '带我看看,你的城市', + '富士Fujifilm圈子', + '街拍纪实手册', + '我用手机修照片', + '街事圈子', + '我们都是城市探险家' + ], + 'excerpt': '「汉口杂片集」冬季的日夜雨晴', + 'favorite_list_prefix': [], + 'favorites': 36, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 271435251, + 'img_id_str': '271435251', + 'title': '001', + 'user_id': 1517263, + 'width': 1079 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 105760527, + 'img_id_str': '105760527', + 'title': '001', + 'user_id': 1517263, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 306365934, + 'img_id_str': '306365934', + 'title': '001', + 'user_id': 1517263, + 'width': 1079 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1279, + 'img_id': 463128718, + 'img_id_str': '463128718', + 'title': '001', + 'user_id': 1517263, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 610387702, + 'img_id_str': '610387702', + 'title': '001', + 'user_id': 1517263, + 'width': 1279 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1279, + 'img_id': 436521125, + 'img_id_str': '436521125', + 'title': '001', + 'user_id': 1517263, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1279, + 'img_id': 422627760, + 'img_id_str': '422627760', + 'title': '001', + 'user_id': 1517263, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 542623416, + 'img_id_str': '542623416', + 'title': '001', + 'user_id': 1517263, + 'width': 1079 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 204981888, + 'img_id_str': '204981888', + 'title': '001', + 'user_id': 1517263, + 'width': 1079 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1281, + 'img_id': 415155849, + 'img_id_str': '415155849', + 'title': '001', + 'user_id': 1517263, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1279, + 'img_id': 209503360, + 'img_id_str': '209503360', + 'title': '001', + 'user_id': 1517263, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 527353774, + 'img_id_str': '527353774', + 'title': '001', + 'user_id': 1517263, + 'width': 1079 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '01月17日', + 'post_id': 61779732, + 'published_at': '2020-01-17 10:32:01', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深纪实摄影师', + 'domain': 'whoken.tuchong.com', + 'followers': 1785, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1517263_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '狼来了OKEN', + 'site_id': '1517263', + 'type': 'user', + 'url': 'https://whoken.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深纪实摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1517263', + 'sites': [], + 'tags': [ + '武汉城记', + '城市·夜晚', + '带我看看,你的城市', + '富士Fujifilm圈子', + '街拍纪实手册', + '我用手机修照片' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://whoken.tuchong.com/61779732/', + 'views': 1260 + }, + { + 'author_id': '2400018', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 10, + 'content': 'PAKSE@CITY.LAOS ...', + 'created_at': '2020-01-19 09:13:38', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '异国风光情报站', + '晒晒旅行打卡照', + '图虫旅行摄影圈子', + '人文纪实手册', + '人文天下', + '风光人文地理', + '闪迪旅拍大赛' + ], + 'excerpt': 'PAKSE@CITY.LAOS ...', + 'favorite_list_prefix': [], + 'favorites': 50, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1298, + 'img_id': 82691755, + 'img_id_str': '82691755', + 'title': '', + 'user_id': 2400018, + 'width': 2048 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '7', + 'passed_time': '01月19日', + 'post_id': 61831585, + 'published_at': '2020-01-19 09:13:38', + 'recommend': true, + 'recom_type': '', + 'rewardable': false, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 2, + 'site': { + 'description': '资深旅行摄影师', + 'domain': '', + 'followers': 5553, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2400018_21', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'FOX86', + 'site_id': '2400018', + 'type': 'user', + 'url': 'https://tuchong.com/2400018/', + 'verification_list': [ + {'verification_reason': '资深旅行摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2400018', + 'sites': [], + 'tags': ['异国风光情报站', '晒晒旅行打卡照', '图虫旅行摄影圈子', '人文纪实手册', '人文天下', '风光人文地理'], + 'title': '... 讀ღ萬象(PAKSE.紀實-28)', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2400018/61831585/', + 'views': 1402 + }, + { + 'author_id': '1945520', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 3, + 'content': '涩谷繁华处', + 'created_at': '2020-01-10 16:28:41', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '生活小确幸', + '美图汇', + '取景器里的小美好', + '街拍纪实手册', + '我要上开屏', + '我们都是城市探险家' + ], + 'excerpt': '涩谷繁华处', + 'favorite_list_prefix': [], + 'favorites': 36, + 'image_count': 5, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4096, + 'img_id': 295356207, + 'img_id_str': '295356207', + 'title': '001', + 'user_id': 1945520, + 'width': 2304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5168, + 'img_id': 223791022, + 'img_id_str': '223791022', + 'title': '001', + 'user_id': 1945520, + 'width': 3448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 633325604, + 'img_id_str': '633325604', + 'title': '001', + 'user_id': 1945520, + 'width': 5168 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2907, + 'img_id': 459130845, + 'img_id_str': '459130845', + 'title': '001', + 'user_id': 1945520, + 'width': 5168 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2870, + 'img_id': 463848751, + 'img_id_str': '463848751', + 'title': '001', + 'user_id': 1945520, + 'width': 5102 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '01月10日', + 'post_id': 61579799, + 'published_at': '2020-01-10 16:28:41', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深旅行摄影师', + 'domain': '', + 'followers': 5454, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1945520_7', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影师芊芊', + 'site_id': '1945520', + 'type': 'user', + 'url': 'https://tuchong.com/1945520/', + 'verification_list': [ + {'verification_reason': '资深旅行摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1945520', + 'sites': [], + 'tags': ['生活小确幸', '美图汇', '取景器里的小美好', '街拍纪实手册', '我要上开屏', '我们都是城市探险家'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1945520/61579799/', + 'views': 1594 + }, + { + 'author_id': '3379664', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 6, + 'content': '———?ここらで休んでみませんか,ねぇうちにおいで温めてあげるよ?———', + 'created_at': '2019-12-27 23:05:28', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['日系集', '分享神仙颜值', '光环摄影美学'], + 'excerpt': '———?ここらで休んでみませんか,ねぇうちにおいで温めてあげるよ?———', + 'favorite_list_prefix': [], + 'favorites': 43, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 210617534, + 'img_id_str': '210617534', + 'title': '001', + 'user_id': 3379664, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5436, + 'img_id': 339526919, + 'img_id_str': '339526919', + 'title': '001', + 'user_id': 3379664, + 'width': 3050 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4859, + 'img_id': 43173048, + 'img_id_str': '43173048', + 'title': '001', + 'user_id': 3379664, + 'width': 2993 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 53003381, + 'img_id_str': '53003381', + 'title': '001', + 'user_id': 3379664, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3921, + 'img_id': 260425116, + 'img_id_str': '260425116', + 'title': '001', + 'user_id': 3379664, + 'width': 2736 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2995, + 'img_id': 247383422, + 'img_id_str': '247383422', + 'title': '001', + 'user_id': 3379664, + 'width': 5577 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5065, + 'img_id': 616416612, + 'img_id_str': '616416612', + 'title': '001', + 'user_id': 3379664, + 'width': 2997 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4712, + 'img_id': 409846993, + 'img_id_str': '409846993', + 'title': '001', + 'user_id': 3379664, + 'width': 2944 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 438027803, + 'img_id_str': '438027803', + 'title': '001', + 'user_id': 3379664, + 'width': 4000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '4', + 'passed_time': '2019年12月27日', + 'post_id': 61066271, + 'published_at': '2019-12-27 23:05:28', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '一个喜欢汉服,日系的摄影,拍自己喜欢的人事物,然后吸引喜欢自己的人。微博同名,约片请加wx763343787', + 'domain': '', + 'followers': 36, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3379664_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '陈司空Freedom', + 'site_id': '3379664', + 'type': 'user', + 'url': 'https://tuchong.com/3379664/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '3379664', + 'sites': [], + 'tags': ['日系集', '分享神仙颜值', '光环摄影美学', '秋季', '人像', 'JK'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3379664/61066271/', + 'views': 2302 + }, + { + 'author_id': '1142826', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 10, + 'content': '乡村的小房子', + 'created_at': '2019-12-25 10:33:08', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '最满意的风光照', + '北京自然风光小组', + '极致风光摄影', + '风光摄影圈', + '图虫旅行摄影圈子', + '图虫封面你做主', + '直到世界的尽头', + '我们都在孤独星球' + ], + 'excerpt': '乡村的小房子', + 'favorite_list_prefix': [], + 'favorites': 99, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 7360, + 'img_id': 459326964, + 'img_id_str': '459326964', + 'title': '961590', + 'user_id': 1142826, + 'width': 4912 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '10', + 'passed_time': '2019年12月25日', + 'post_id': 60950933, + 'published_at': '2019-12-25 10:33:08', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深风光摄影师', + 'domain': 'kiddhuang.tuchong.com', + 'followers': 7405, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1142826_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '哈哈熊Kidd_Huang', + 'site_id': '1142826', + 'type': 'user', + 'url': 'https://kiddhuang.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1142826', + 'sites': [], + 'tags': ['最满意的风光照', '北京自然风光小组', '极致风光摄影', '风光摄影圈', '图虫旅行摄影圈子', '图虫封面你做主'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://kiddhuang.tuchong.com/60950933/', + 'views': 2728 + }, + { + 'author_id': '1903140', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 8, + 'content': null, + 'created_at': '2020-01-07 11:51:51', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 3, + 'image_count': 0, + 'images': [], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '5', + 'passed_time': '01月07日', + 'post_id': 61481339, + 'published_at': '2020-01-07 11:51:51', + 'recommend': true, + 'recom_type': '', + 'rewardable': false, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '努力有朝一日拍出糖水片', + 'domain': '', + 'followers': 397, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1903140_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '炸酱面同学', + 'site_id': '1903140', + 'type': 'user', + 'url': 'https://tuchong.com/1903140/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '1903140', + 'sites': [], + 'tags': ['评论斗图大赛'], + 'title': '你有什么关于摄影的表情包吗?评论里发出来给大家看看', + 'title_image': { + 'width': 270, + 'height': 270, + 'url': 'https://tuchong.pstatp.com/1903140/f/146916790.jpg' + }, + 'type': 'text', + 'update': false, + 'url': 'https://tuchong.com/1903140/t/61481339/', + 'views': 336 + }, + { + 'author_id': '7159375', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 3, + 'content': '蛋黄酥', + 'created_at': '2019-12-20 09:49:53', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['拍拍好吃的'], + 'excerpt': '蛋黄酥', + 'favorite_list_prefix': [], + 'favorites': 65, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 75154568, + 'img_id_str': '75154568', + 'title': '001', + 'user_id': 7159375, + 'width': 3329 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 634373825, + 'img_id_str': '634373825', + 'title': '001', + 'user_id': 7159375, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 160941388, + 'img_id_str': '160941388', + 'title': '001', + 'user_id': 7159375, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 457229653, + 'img_id_str': '457229653', + 'title': '001', + 'user_id': 7159375, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 590988106, + 'img_id_str': '590988106', + 'title': '001', + 'user_id': 7159375, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 55493468, + 'img_id_str': '55493468', + 'title': '001', + 'user_id': 7159375, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 420267109, + 'img_id_str': '420267109', + 'title': '001', + 'user_id': 7159375, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 423609431, + 'img_id_str': '423609431', + 'title': '001', + 'user_id': 7159375, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 436388774, + 'img_id_str': '436388774', + 'title': '001', + 'user_id': 7159375, + 'width': 3456 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '2019年12月20日', + 'post_id': 60724371, + 'published_at': '2019-12-20 09:49:53', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 6, + 'site': { + 'description': '资深美食摄影师', + 'domain': '', + 'followers': 3863, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_7159375_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '薯Mica', + 'site_id': '7159375', + 'type': 'user', + 'url': 'https://tuchong.com/7159375/', + 'verification_list': [ + {'verification_reason': '资深美食摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '7159375', + 'sites': [], + 'tags': ['拍拍好吃的', '早餐', '静物', '茶点', '糕点', '美味的'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/7159375/60724371/', + 'views': 2683 + }, + { + 'author_id': '11009227', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 6, + 'content': '竹里馆', + 'created_at': '2019-12-11 12:10:48', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['对短发姑娘没有任何抵抗力!'], + 'excerpt': '竹里馆', + 'favorite_list_prefix': [], + 'favorites': 305, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 354403197, + 'img_id_str': '354403197', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 227722302, + 'img_id_str': '227722302', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 586269701, + 'img_id_str': '586269701', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 223331333, + 'img_id_str': '223331333', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2136, + 'img_id': 265078230, + 'img_id_str': '265078230', + 'title': '001', + 'user_id': 11009227, + 'width': 3200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 307610344, + 'img_id_str': '307610344', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 564052764, + 'img_id_str': '564052764', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 516276848, + 'img_id_str': '516276848', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 603571190, + 'img_id_str': '603571190', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 109429460, + 'img_id_str': '109429460', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 321962809, + 'img_id_str': '321962809', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 286049066, + 'img_id_str': '286049066', + 'title': '001', + 'user_id': 11009227, + 'width': 2136 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '6', + 'passed_time': '2019年12月11日', + 'post_id': 60255004, + 'published_at': '2019-12-11 12:10:48', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '2', + 'rqt_id': '', + 'shares': 9, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 9783, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_11009227_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '星野森', + 'site_id': '11009227', + 'type': 'user', + 'url': 'https://tuchong.com/11009227/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '11009227', + 'sites': [], + 'tags': ['对短发姑娘没有任何抵抗力!', '短裙', '大自然', '竹', 'JK', 'JK制服'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/11009227/60255004/', + 'views': 16244 + }, + { + 'author_id': '1667050', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '', + 'created_at': '2020-01-02 09:38:12', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 28, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4788, + 'img_id': 618579729, + 'img_id_str': '618579729', + 'title': '', + 'user_id': 1667050, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4920, + 'img_id': 447006399, + 'img_id_str': '447006399', + 'title': '', + 'user_id': 1667050, + 'width': 3204 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4104, + 'img_id': 634243064, + 'img_id_str': '634243064', + 'title': '', + 'user_id': 1667050, + 'width': 2736 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4896, + 'img_id': 377275805, + 'img_id_str': '377275805', + 'title': '', + 'user_id': 1667050, + 'width': 3156 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4248, + 'img_id': 212714957, + 'img_id_str': '212714957', + 'title': '', + 'user_id': 1667050, + 'width': 2736 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4104, + 'img_id': 209569416, + 'img_id_str': '209569416', + 'title': '', + 'user_id': 1667050, + 'width': 2736 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4104, + 'img_id': 84788031, + 'img_id_str': '84788031', + 'title': '', + 'user_id': 1667050, + 'width': 2736 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4104, + 'img_id': 478791647, + 'img_id_str': '478791647', + 'title': '', + 'user_id': 1667050, + 'width': 2736 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4692, + 'img_id': 351388740, + 'img_id_str': '351388740', + 'title': '', + 'user_id': 1667050, + 'width': 3060 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4104, + 'img_id': 643876974, + 'img_id_str': '643876974', + 'title': '', + 'user_id': 1667050, + 'width': 2736 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4104, + 'img_id': 463783297, + 'img_id_str': '463783297', + 'title': '', + 'user_id': 1667050, + 'width': 2736 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月02日', + 'post_id': 61313180, + 'published_at': '2020-01-02 09:38:12', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '每个人都有一份礼物,藏在内心的最深处~', + 'domain': '', + 'followers': 14195, + 'has_everphoto_note': false, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1667050_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '如馨Lee', + 'site_id': '1667050', + 'type': 'user', + 'url': 'https://tuchong.com/1667050/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '1667050', + 'sites': [], + 'tags': ['色彩', '风光', '旅行', '抓拍', '人像', '美女'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1667050/61313180/', + 'views': 1301 + }, + { + 'author_id': '3229117', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 44, + 'content': '这个夏天,晚上一起散步吧,可以的话,带上你的猫。', + 'created_at': '2019-10-17 11:07:52', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '日系集', + '咔了个图', + '糖水人像小组', + '高颜值女神聚集地', + '人像爱好者', + '我要上“首页推荐位”', + '分享神仙颜值', + '趣味话题:唯美如你', + '冷色调人像', + '人像写真摄影约拍', + '2019你最满意的照片' + ], + 'excerpt': '这个夏天,晚上一起散步吧,可以的话,带上你的猫。', + 'favorite_list_prefix': [], + 'favorites': 987, + 'image_count': 45, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 273986266, + 'img_id_str': '273986266', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2856, + 'img_id': 368882422, + 'img_id_str': '368882422', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 161264202, + 'img_id_str': '161264202', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 505918265, + 'img_id_str': '505918265', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 564048864, + 'img_id_str': '564048864', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3556, + 'img_id': 425505798, + 'img_id_str': '425505798', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 134067048, + 'img_id_str': '134067048', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3047, + 'img_id': 208843622, + 'img_id_str': '208843622', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3037, + 'img_id': 64992636, + 'img_id_str': '64992636', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 644723734, + 'img_id_str': '644723734', + 'title': '001', + 'user_id': 3229117, + 'width': 3018 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2979, + 'img_id': 266777684, + 'img_id_str': '266777684', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1125, + 'img_id': 312390413, + 'img_id_str': '312390413', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 371569671, + 'img_id_str': '371569671', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 502641792, + 'img_id_str': '502641792', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3049, + 'img_id': 435204882, + 'img_id_str': '435204882', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3030, + 'img_id': 267039729, + 'img_id_str': '267039729', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 267039730, + 'img_id_str': '267039730', + 'title': '001', + 'user_id': 3229117, + 'width': 3556 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 63353766, + 'img_id_str': '63353766', + 'title': '001', + 'user_id': 3229117, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 189183066, + 'img_id_str': '189183066', + 'title': '001', + 'user_id': 3229117, + 'width': 3556 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 414823412, + 'img_id_str': '414823412', + 'title': '001', + 'user_id': 3229117, + 'width': 3176 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 500937802, + 'img_id_str': '500937802', + 'title': '001', + 'user_id': 3229117, + 'width': 3029 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 219460752, + 'img_id_str': '219460752', + 'title': '001', + 'user_id': 3229117, + 'width': 3556 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 475378723, + 'img_id_str': '475378723', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 157463197, + 'img_id_str': '157463197', + 'title': '001', + 'user_id': 3229117, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2976, + 'img_id': 507491069, + 'img_id_str': '507491069', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 227193537, + 'img_id_str': '227193537', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 247575440, + 'img_id_str': '247575440', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 394769119, + 'img_id_str': '394769119', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 444642556, + 'img_id_str': '444642556', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 457421692, + 'img_id_str': '457421692', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3061, + 'img_id': 616018937, + 'img_id_str': '616018937', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 211858290, + 'img_id_str': '211858290', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3047, + 'img_id': 651343059, + 'img_id_str': '651343059', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 157135706, + 'img_id_str': '157135706', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2940, + 'img_id': 563721683, + 'img_id_str': '563721683', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 103199939, + 'img_id_str': '103199939', + 'title': '001', + 'user_id': 3229117, + 'width': 3556 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 80458658, + 'img_id_str': '80458658', + 'title': '001', + 'user_id': 3229117, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 464433878, + 'img_id_str': '464433878', + 'title': '001', + 'user_id': 3229117, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 470987678, + 'img_id_str': '470987678', + 'title': '001', + 'user_id': 3229117, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 581350208, + 'img_id_str': '581350208', + 'title': '001', + 'user_id': 3229117, + 'width': 3556 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 71152722, + 'img_id_str': '71152722', + 'title': '001', + 'user_id': 3229117, + 'width': 3555 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 228504107, + 'img_id_str': '228504107', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 313439599, + 'img_id_str': '313439599', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 392606487, + 'img_id_str': '392606487', + 'title': '001', + 'user_id': 3229117, + 'width': 3556 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 597079052, + 'img_id_str': '597079052', + 'title': '001', + 'user_id': 3229117, + 'width': 2000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '34', + 'passed_time': '2019年10月17日', + 'post_id': 56119158, + 'published_at': '2019-10-17 11:07:52', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 38, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 3071, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3229117_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '黑猫阿姨', + 'site_id': '3229117', + 'type': 'user', + 'url': 'https://tuchong.com/3229117/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3229117', + 'sites': [], + 'tags': ['日系集', '咔了个图', '糖水人像小组', '高颜值女神聚集地', '人像爱好者', '我要上“首页推荐位”'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3229117/56119158/', + 'views': 44503 + }, + { + 'author_id': '5417230', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '深圳冰与火三叉戟', + 'created_at': '2020-01-18 15:46:09', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['图虫城市建筑'], + 'excerpt': '深圳冰与火三叉戟', + 'favorite_list_prefix': [], + 'favorites': 17, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5309, + 'img_id': 534759491, + 'img_id_str': '534759491', + 'title': '001', + 'user_id': 5417230, + 'width': 7955 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '01月18日', + 'post_id': 61813662, + 'published_at': '2020-01-18 15:46:09', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 173, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_5417230_5', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'Franky-Wang', + 'site_id': '5417230', + 'type': 'user', + 'url': 'https://tuchong.com/5417230/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '5417230', + 'sites': [], + 'tags': ['图虫城市建筑', '天际线', '日落', '建筑', '城市', '薄暮'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/5417230/61813662/', + 'views': 694 + }, + { + 'author_id': '2441927', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': '今年最后一次的随心拍摄!', + 'created_at': '2019-12-28 17:47:00', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '纯粹黑白圈子', + '人像写真摄影约拍', + '人像爱好者', + '我要上开屏', + '我要上“首页推荐位”', + '绝不停止记录的2019', + '做人要低调,摄影也是', + '人像摄影集', + '无创意,不摄影', + '分享决定性瞬间' + ], + 'excerpt': '今年最后一次的随心拍摄!', + 'favorite_list_prefix': [], + 'favorites': 32, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5048, + 'img_id': 293390358, + 'img_id_str': '293390358', + 'title': '1540309', + 'user_id': 2441927, + 'width': 5048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2990, + 'img_id': 561366087, + 'img_id_str': '561366087', + 'title': '1540307', + 'user_id': 2441927, + 'width': 2990 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2992, + 'img_id': 444056532, + 'img_id_str': '444056532', + 'title': '1540305', + 'user_id': 2441927, + 'width': 2992 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3078, + 'img_id': 301319583, + 'img_id_str': '301319583', + 'title': '1540308', + 'user_id': 2441927, + 'width': 3078 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3998, + 'img_id': 366397143, + 'img_id_str': '366397143', + 'title': '1540311', + 'user_id': 2441927, + 'width': 7108 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3002, + 'img_id': 253150255, + 'img_id_str': '253150255', + 'title': '1540306', + 'user_id': 2441927, + 'width': 3002 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5036, + 'img_id': 586139225, + 'img_id_str': '586139225', + 'title': '1540310', + 'user_id': 2441927, + 'width': 5036 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3020, + 'img_id': 378651973, + 'img_id_str': '378651973', + 'title': '1540304', + 'user_id': 2441927, + 'width': 3020 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2998, + 'img_id': 361219570, + 'img_id_str': '361219570', + 'title': '1540312', + 'user_id': 2441927, + 'width': 2998 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '2019年12月28日', + 'post_id': 61099958, + 'published_at': '2019-12-28 17:47:00', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 1823, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2441927_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '玩摄影的鱼', + 'site_id': '2441927', + 'type': 'user', + 'url': 'https://tuchong.com/2441927/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2441927', + 'sites': [], + 'tags': [ + '纯粹黑白圈子', + '人像写真摄影约拍', + '人像爱好者', + '我要上开屏', + '我要上“首页推荐位”', + '绝不停止记录的2019' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2441927/61099958/', + 'views': 1415 + }, + { + 'author_id': '384622', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '圣诞?画报女孩\n拍摄:佐小夕 出镜:嘉鱼儿\nblingbling圣诞girl\n韩国女团的赶脚 ✨', + 'created_at': '2019-12-24 17:07:41', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['人像爱好者'], + 'excerpt': '圣诞?画报女孩\n拍摄:佐小夕 出镜:嘉鱼儿\nblingbling圣诞girl\n韩国女团的赶脚 ✨', + 'favorite_list_prefix': [], + 'favorites': 109, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 556582696, + 'img_id_str': '556582696', + 'title': '001', + 'user_id': 384622, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 589874927, + 'img_id_str': '589874927', + 'title': '001', + 'user_id': 384622, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 547800382, + 'img_id_str': '547800382', + 'title': '001', + 'user_id': 384622, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1200, + 'img_id': 261473603, + 'img_id_str': '261473603', + 'title': '001', + 'user_id': 384622, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 408208572, + 'img_id_str': '408208572', + 'title': '001', + 'user_id': 384622, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1200, + 'img_id': 282314237, + 'img_id_str': '282314237', + 'title': '001', + 'user_id': 384622, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 603309190, + 'img_id_str': '603309190', + 'title': '001', + 'user_id': 384622, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 618645148, + 'img_id_str': '618645148', + 'title': '001', + 'user_id': 384622, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1216, + 'img_id': 112969306, + 'img_id_str': '112969306', + 'title': '001', + 'user_id': 384622, + 'width': 1823 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 146130245, + 'img_id_str': '146130245', + 'title': '001', + 'user_id': 384622, + 'width': 1200 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '4', + 'passed_time': '2019年12月24日', + 'post_id': 60920602, + 'published_at': '2019-12-24 17:07:41', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 11, + 'site': { + 'description': '新浪微博:@佐小夕 摄影教学班微博持续招生中~~~', + 'domain': 'zuoxiaoxi.tuchong.com', + 'followers': 6350, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_384622_6', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '佐小夕', + 'site_id': '384622', + 'type': 'user', + 'url': 'https://zuoxiaoxi.tuchong.com/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '384622', + 'sites': [], + 'tags': ['人像爱好者', '人像', '美女', '圣诞节', '时尚', '性感的'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://zuoxiaoxi.tuchong.com/60920602/', + 'views': 3709 + }, + { + 'author_id': '15787944', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 54, + 'content': null, + 'created_at': '2019-12-25 09:34:03', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['胶片人文和纪实交流', '胶片人像摄影', '胶片集'], + 'excerpt': + '现在看去电影院看电影,大部分是数码影片了,也有胶卷版,很少播放了。你在家网络看在线?或者下载?都是数码版了无论静态相片和动态视频,数码胶片早就不用讨论了', + 'favorite_list_prefix': [], + 'favorites': 1, + 'image_count': 0, + 'images': [], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '43', + 'passed_time': '2019年12月25日', + 'post_id': 60947952, + 'published_at': '2019-12-25 09:34:03', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 3, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15787944_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '七分之一的想念', + 'site_id': '15787944', + 'type': 'user', + 'url': 'https://tuchong.com/15787944/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15787944', + 'sites': [], + 'tags': ['胶片人文和纪实交流', '胶片人像摄影', '胶片集'], + 'title': '你觉得胶片机的时代过去了吗?为什么?', + 'title_image': null, + 'type': 'text', + 'update': false, + 'url': 'https://tuchong.com/15787944/t/60947952/', + 'views': 783 + }, + { + 'author_id': '1732078', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 8, + 'content': '猎德大桥。', + 'created_at': '2020-01-02 09:37:26', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + 'E▪shutter', + '图虫旅行摄影圈子', + '光影者的风光摄影', + '图虫城市建筑', + '极致风光摄影', + '最满意的风光照', + '我们都是城市探险家', + '风光摄影圈', + '一起拍建筑', + '直到世界的尽头' + ], + 'excerpt': '猎德大桥。', + 'favorite_list_prefix': [], + 'favorites': 41, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 316655214, + 'img_id_str': '316655214', + 'title': '001', + 'user_id': 1732078, + 'width': 1125 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '8', + 'passed_time': '01月02日', + 'post_id': 61313157, + 'published_at': '2020-01-02 09:37:26', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 2, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 1389, + 'has_everphoto_note': false, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1732078_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '一念初见丶', + 'site_id': '1732078', + 'type': 'user', + 'url': 'https://tuchong.com/1732078/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1732078', + 'sites': [], + 'tags': [ + 'E▪shutter', + '图虫旅行摄影圈子', + '光影者的风光摄影', + '图虫城市建筑', + '极致风光摄影', + '最满意的风光照' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1732078/61313157/', + 'views': 2108 + }, + { + 'author_id': '4296004', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': 'A Dream', + 'created_at': '2020-01-13 18:36:49', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['人像写真摄影约拍'], + 'excerpt': 'A Dream', + 'favorite_list_prefix': [], + 'favorites': 10, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 79546086, + 'img_id_str': '79546086', + 'title': '001', + 'user_id': 4296004, + 'width': 2690 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5918, + 'img_id': 153208780, + 'img_id_str': '153208780', + 'title': '001', + 'user_id': 4296004, + 'width': 3947 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 488883546, + 'img_id_str': '488883546', + 'title': '001', + 'user_id': 4296004, + 'width': 2689 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 479315652, + 'img_id_str': '479315652', + 'title': '001', + 'user_id': 4296004, + 'width': 2690 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2690, + 'img_id': 361875193, + 'img_id_str': '361875193', + 'title': '001', + 'user_id': 4296004, + 'width': 4032 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 513132289, + 'img_id_str': '513132289', + 'title': '001', + 'user_id': 4296004, + 'width': 2690 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 259377099, + 'img_id_str': '259377099', + 'title': '001', + 'user_id': 4296004, + 'width': 2690 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 399165599, + 'img_id_str': '399165599', + 'title': '001', + 'user_id': 4296004, + 'width': 2690 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4084, + 'img_id': 277137153, + 'img_id_str': '277137153', + 'title': '001', + 'user_id': 4296004, + 'width': 6122 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月13日', + 'post_id': 61676200, + 'published_at': '2020-01-13 18:36:49', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深旅行摄影师', + 'domain': '', + 'followers': 554, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_4296004_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'BonBon的Pan', + 'site_id': '4296004', + 'type': 'user', + 'url': 'https://tuchong.com/4296004/', + 'verification_list': [ + {'verification_reason': '资深旅行摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '4296004', + 'sites': [], + 'tags': ['人像写真摄影约拍', '北海道', '二世古', '羊蹄山', '风光', '色彩'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/4296004/61676200/', + 'views': 310 + }, + { + 'author_id': '7848088', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '瓦拉纳西的印度教徒清晨起来打扫他的住所', + 'created_at': '2019-12-18 21:20:54', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['2019你最满意的照片'], + 'excerpt': '瓦拉纳西的印度教徒清晨起来打扫他的住所', + 'favorite_list_prefix': [], + 'favorites': 34, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5503, + 'img_id': 357090789, + 'img_id_str': '357090789', + 'title': '1563797', + 'user_id': 7848088, + 'width': 3598 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '2019年12月18日', + 'post_id': 60662919, + 'published_at': '2019-12-18 21:20:54', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '鸟人', + 'domain': '', + 'followers': 59, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_7848088_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '琛羽', + 'site_id': '7848088', + 'type': 'user', + 'url': 'https://tuchong.com/7848088/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '7848088', + 'sites': [], + 'tags': ['2019你最满意的照片', '2019-旅程'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/7848088/60662919/', + 'views': 1384 + }, + { + 'author_id': '2726856', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '白鹭相伴', + 'created_at': '2020-01-02 09:57:15', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['中国手机摄影'], + 'excerpt': '白鹭相伴', + 'favorite_list_prefix': [], + 'favorites': 56, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 637388692, + 'img_id_str': '637388692', + 'title': '001', + 'user_id': 2726856, + 'width': 1202 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '4', + 'passed_time': '01月02日', + 'post_id': 61313646, + 'published_at': '2020-01-02 09:57:15', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 3, + 'site': { + 'description': '第三届京东金像奖年度作品奖获得者', + 'domain': '', + 'followers': 5231, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2726856_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'ALUphotography', + 'site_id': '2726856', + 'type': 'user', + 'url': 'https://tuchong.com/2726856/', + 'verification_list': [ + {'verification_reason': '第三届京东金像奖年度作品奖获得者', 'verification_type': 12} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2726856', + 'sites': [], + 'tags': ['中国手机摄影', '风光', '旅行', '色彩', '城市', '亲子'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2726856/61313646/', + 'views': 2138 + }, + { + 'author_id': '1028071', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 11, + 'content': 'Beauty', + 'created_at': '2019-12-20 14:18:55', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['高颜值女神聚集地', '分享神仙颜值'], + 'excerpt': 'Beauty', + 'favorite_list_prefix': [], + 'favorites': 195, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2208, + 'img_id': 126075906, + 'img_id_str': '126075906', + 'title': '001', + 'user_id': 1028071, + 'width': 1242 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2208, + 'img_id': 494847231, + 'img_id_str': '494847231', + 'title': '001', + 'user_id': 1028071, + 'width': 1242 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2208, + 'img_id': 184730593, + 'img_id_str': '184730593', + 'title': '001', + 'user_id': 1028071, + 'width': 1242 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2208, + 'img_id': 191743002, + 'img_id_str': '191743002', + 'title': '001', + 'user_id': 1028071, + 'width': 1242 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2208, + 'img_id': 609993411, + 'img_id_str': '609993411', + 'title': '001', + 'user_id': 1028071, + 'width': 1242 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2208, + 'img_id': 506971060, + 'img_id_str': '506971060', + 'title': '001', + 'user_id': 1028071, + 'width': 1242 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2208, + 'img_id': 361153315, + 'img_id_str': '361153315', + 'title': '001', + 'user_id': 1028071, + 'width': 1242 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2208, + 'img_id': 132498581, + 'img_id_str': '132498581', + 'title': '001', + 'user_id': 1028071, + 'width': 1242 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2208, + 'img_id': 379307530, + 'img_id_str': '379307530', + 'title': '001', + 'user_id': 1028071, + 'width': 1242 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '11', + 'passed_time': '2019年12月20日', + 'post_id': 60733833, + 'published_at': '2019-12-20 14:18:55', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '5', + 'rqt_id': '', + 'shares': 8, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'ysgart.tuchong.com', + 'followers': 1370, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1028071_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '一束光ART', + 'site_id': '1028071', + 'type': 'user', + 'url': 'https://ysgart.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 12} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1028071', + 'sites': [], + 'tags': ['高颜值女神聚集地', '分享神仙颜值', '时尚', '漂亮的', '肖像', '人'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://ysgart.tuchong.com/60733833/', + 'views': 11206 + }, + { + 'author_id': '1', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 20, + 'content': null, + 'created_at': '2020-01-02 17:05:56', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '浙江摄影俱乐部', + '手机摄影小分队', + '华为摄影爱好者', + '极简主义摄影', + '极简主义,少即是多', + '火烛一花精品摄影', + '我爱街拍摄影小组圈子', + '生活小确幸', + '美图汇', + '静物美学' + ], + 'excerpt': + '第六期圈子互动排行榜重磅出炉!每周小秘书会统计大家在圈子里发布的评论,按照圈子成员的人均评论数由高到低进行排名。恭喜以下摄影师管理的圈子成功上榜 !✿✿ヽ( ̄▽ ̄)ノ✿撒花撒花~~~第一名:静物美学圈主:@林大胖本胖第二名:美图汇圈主:@琥珀Hooper第三名:生活小确幸圈主@恬梦想第四名:我爱街拍摄影小组圈子圈主:@葫韵雅笛第五名:火烛一花精品摄影圈主@火烛鸣花第六名:极简主义,少即是多圈主@藏在取景器后的鹿先森...', + 'favorite_list_prefix': [], + 'favorites': 118, + 'image_count': 0, + 'images': [], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '13', + 'passed_time': '01月02日', + 'post_id': 61325862, + 'published_at': '2020-01-02 17:05:56', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 7, + 'site': { + 'description': '图虫官方账号', + 'domain': 'webmaster.tuchong.com', + 'followers': 9932336, + 'has_everphoto_note': true, + 'icon': 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '图虫小秘书', + 'site_id': '1', + 'type': 'user', + 'url': 'https://webmaster.tuchong.com/', + 'verification_list': [ + {'verification_reason': '图虫官方账号', 'verification_type': 11} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1', + 'sites': [], + 'tags': [ + '浙江摄影俱乐部', + '手机摄影小分队', + '华为摄影爱好者', + '极简主义摄影', + '极简主义,少即是多', + '火烛一花精品摄影' + ], + 'title': '第六期圈子互动排行榜重磅出炉!', + 'title_image': { + 'width': 600, + 'height': 355, + 'url': 'https://tuchong.pstatp.com/1/l/94228315.jpg' + }, + 'type': 'text', + 'update': false, + 'url': 'https://webmaster.tuchong.com/t/61325862/', + 'views': 23235 + }, + { + 'author_id': '332120', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '冬天想念着夏天,秋天期待着春天。淡淡的期盼最美。', + 'created_at': '2019-12-30 18:14:06', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['高颜值女神聚集地', '人像爱好者', '暖色调人像', '成都约拍圈子', '分享神仙颜值'], + 'excerpt': '冬天想念着夏天,秋天期待着春天。淡淡的期盼最美。', + 'favorite_list_prefix': [], + 'favorites': 62, + 'image_count': 3, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2600, + 'img_id': 616285717, + 'img_id_str': '616285717', + 'title': '463571', + 'user_id': 332120, + 'width': 1734 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2600, + 'img_id': 469484695, + 'img_id_str': '469484695', + 'title': '463572', + 'user_id': 332120, + 'width': 1733 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2600, + 'img_id': 582665618, + 'img_id_str': '582665618', + 'title': '463573', + 'user_id': 332120, + 'width': 1733 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '4', + 'passed_time': '2019年12月30日', + 'post_id': 61191162, + 'published_at': '2019-12-30 18:14:06', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 1917, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_332120_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '陈皮兔的片场', + 'site_id': '332120', + 'type': 'user', + 'url': 'https://tuchong.com/332120/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '332120', + 'sites': [], + 'tags': ['高颜值女神聚集地', '人像爱好者', '暖色调人像', '成都约拍圈子', '分享神仙颜值', '女孩'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/332120/61191162/', + 'views': 3115 + }, + { + 'author_id': '3330966', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '是一个关于旅行的故事~\n出镜:南水 小一', + 'created_at': '2019-12-29 21:08:14', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '是一个关于旅行的故事~\n出镜:南水 小一', + 'favorite_list_prefix': [], + 'favorites': 46, + 'image_count': 8, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 271631127, + 'img_id_str': '271631127', + 'title': '', + 'user_id': 3330966, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 478266846, + 'img_id_str': '478266846', + 'title': '', + 'user_id': 3330966, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 247121572, + 'img_id_str': '247121572', + 'title': '', + 'user_id': 3330966, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 481412215, + 'img_id_str': '481412215', + 'title': '', + 'user_id': 3330966, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5859, + 'img_id': 364758555, + 'img_id_str': '364758555', + 'title': '', + 'user_id': 3330966, + 'width': 3906 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 515294557, + 'img_id_str': '515294557', + 'title': '', + 'user_id': 3330966, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 315804647, + 'img_id_str': '315804647', + 'title': '', + 'user_id': 3330966, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 212846078, + 'img_id_str': '212846078', + 'title': '', + 'user_id': 3330966, + 'width': 4000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '2019年12月29日', + 'post_id': 61155623, + 'published_at': '2019-12-29 21:08:14', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 12, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 3097, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3330966_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '与倾-', + 'site_id': '3330966', + 'type': 'user', + 'url': 'https://tuchong.com/3330966/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3330966', + 'sites': [], + 'tags': ['人像', '旅行', '南京', 'Sony', 'lolita'], + 'title': '爱与死双子~', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3330966/61155623/', + 'views': 2718 + }, + { + 'author_id': '9089124', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 27, + 'content': '時代在變,記憶不變', + 'created_at': '2019-12-20 14:28:42', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['2019你最满意的照片'], + 'excerpt': '時代在變,記憶不變', + 'favorite_list_prefix': [], + 'favorites': 240, + 'image_count': 47, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5602, + 'img_id': 596951717, + 'img_id_str': '596951717', + 'title': '001', + 'user_id': 9089124, + 'width': 4423 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 297583740, + 'img_id_str': '297583740', + 'title': '001', + 'user_id': 9089124, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 260293341, + 'img_id_str': '260293341', + 'title': '001', + 'user_id': 9089124, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2517, + 'img_id': 330089828, + 'img_id_str': '330089828', + 'title': '001', + 'user_id': 9089124, + 'width': 4043 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3650, + 'img_id': 515884147, + 'img_id_str': '515884147', + 'title': '001', + 'user_id': 9089124, + 'width': 6489 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4566, + 'img_id': 149865482, + 'img_id_str': '149865482', + 'title': '001', + 'user_id': 9089124, + 'width': 3960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3825, + 'img_id': 596493261, + 'img_id_str': '596493261', + 'title': '001', + 'user_id': 9089124, + 'width': 3825 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6017, + 'img_id': 309511243, + 'img_id_str': '309511243', + 'title': '001', + 'user_id': 9089124, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4007, + 'img_id': 124765002, + 'img_id_str': '124765002', + 'title': '001', + 'user_id': 9089124, + 'width': 2254 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3755, + 'img_id': 56148820, + 'img_id_str': '56148820', + 'title': '001', + 'user_id': 9089124, + 'width': 6680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3513, + 'img_id': 324715710, + 'img_id_str': '324715710', + 'title': '001', + 'user_id': 9089124, + 'width': 2823 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3690, + 'img_id': 497272330, + 'img_id_str': '497272330', + 'title': '001', + 'user_id': 9089124, + 'width': 6560 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4299, + 'img_id': 602588072, + 'img_id_str': '602588072', + 'title': '001', + 'user_id': 9089124, + 'width': 4981 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4977, + 'img_id': 379241629, + 'img_id_str': '379241629', + 'title': '001', + 'user_id': 9089124, + 'width': 4477 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 572048529, + 'img_id_str': '572048529', + 'title': '001', + 'user_id': 9089124, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2938, + 'img_id': 288146706, + 'img_id_str': '288146706', + 'title': '001', + 'user_id': 9089124, + 'width': 2595 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 79873366, + 'img_id_str': '79873366', + 'title': '001', + 'user_id': 9089124, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3977, + 'img_id': 356107390, + 'img_id_str': '356107390', + 'title': '001', + 'user_id': 9089124, + 'width': 4935 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3761, + 'img_id': 355910721, + 'img_id_str': '355910721', + 'title': '001', + 'user_id': 9089124, + 'width': 4540 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5119, + 'img_id': 122668590, + 'img_id_str': '122668590', + 'title': '001', + 'user_id': 9089124, + 'width': 3968 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3411, + 'img_id': 242860815, + 'img_id_str': '242860815', + 'title': '001', + 'user_id': 9089124, + 'width': 6137 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3373, + 'img_id': 651609141, + 'img_id_str': '651609141', + 'title': '001', + 'user_id': 9089124, + 'width': 5999 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3211, + 'img_id': 555533821, + 'img_id_str': '555533821', + 'title': '001', + 'user_id': 9089124, + 'width': 5710 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3431, + 'img_id': 612418685, + 'img_id_str': '612418685', + 'title': '001', + 'user_id': 9089124, + 'width': 6101 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3039, + 'img_id': 266519712, + 'img_id_str': '266519712', + 'title': '001', + 'user_id': 9089124, + 'width': 5403 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3343, + 'img_id': 100975829, + 'img_id_str': '100975829', + 'title': '001', + 'user_id': 9089124, + 'width': 5943 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2817, + 'img_id': 102614545, + 'img_id_str': '102614545', + 'title': '001', + 'user_id': 9089124, + 'width': 4699 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3871, + 'img_id': 382911747, + 'img_id_str': '382911747', + 'title': '001', + 'user_id': 9089124, + 'width': 3976 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6328, + 'img_id': 319473207, + 'img_id_str': '319473207', + 'title': '001', + 'user_id': 9089124, + 'width': 4289 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4235, + 'img_id': 570213541, + 'img_id_str': '570213541', + 'title': '001', + 'user_id': 9089124, + 'width': 5946 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5697, + 'img_id': 529188052, + 'img_id_str': '529188052', + 'title': '001', + 'user_id': 9089124, + 'width': 3206 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6188, + 'img_id': 620020812, + 'img_id_str': '620020812', + 'title': '001', + 'user_id': 9089124, + 'width': 3245 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5483, + 'img_id': 147506237, + 'img_id_str': '147506237', + 'title': '001', + 'user_id': 9089124, + 'width': 3084 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5869, + 'img_id': 277660283, + 'img_id_str': '277660283', + 'title': '001', + 'user_id': 9089124, + 'width': 4012 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 610190159, + 'img_id_str': '610190159', + 'title': '001', + 'user_id': 9089124, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 61588625, + 'img_id_str': '61588625', + 'title': '001', + 'user_id': 9089124, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3717, + 'img_id': 611828747, + 'img_id_str': '611828747', + 'title': '001', + 'user_id': 9089124, + 'width': 2306 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 146785537, + 'img_id_str': '146785537', + 'title': '001', + 'user_id': 9089124, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5620, + 'img_id': 259179578, + 'img_id_str': '259179578', + 'title': '001', + 'user_id': 9089124, + 'width': 3161 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5151, + 'img_id': 259703570, + 'img_id_str': '259703570', + 'title': '001', + 'user_id': 9089124, + 'width': 3569 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5787, + 'img_id': 211927861, + 'img_id_str': '211927861', + 'title': '001', + 'user_id': 9089124, + 'width': 3868 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6719, + 'img_id': 531154000, + 'img_id_str': '531154000', + 'title': '001', + 'user_id': 9089124, + 'width': 3779 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5396, + 'img_id': 604423238, + 'img_id_str': '604423238', + 'title': '001', + 'user_id': 9089124, + 'width': 3400 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 150258828, + 'img_id_str': '150258828', + 'title': '001', + 'user_id': 9089124, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 510444586, + 'img_id_str': '510444586', + 'title': '001', + 'user_id': 9089124, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 428459236, + 'img_id_str': '428459236', + 'title': '001', + 'user_id': 9089124, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 118932899, + 'img_id_str': '118932899', + 'title': '001', + 'user_id': 9089124, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '19', + 'passed_time': '2019年12月20日', + 'post_id': 60734171, + 'published_at': '2019-12-20 14:28:42', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 19, + 'site': { + 'description': '资深纪实摄影师', + 'domain': 'sky369369.tuchong.com', + 'followers': 8458, + 'has_everphoto_note': false, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_9089124_4', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '拾光朝圣者', + 'site_id': '9089124', + 'type': 'user', + 'url': 'https://sky369369.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深纪实摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '9089124', + 'sites': [], + 'tags': ['2019你最满意的照片', '风光', '色彩', '城市', '纪实', '抓拍'], + 'title': '時代在變,記憶不變', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://sky369369.tuchong.com/60734171/', + 'views': 9654 + }, + { + 'author_id': '2408598', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '忙碌的圣诞季~', + 'created_at': '2019-12-20 08:20:56', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '上海人像圈子', + '上海大学摄影圈', + '图虫旅行摄影圈子', + '高颜值女神聚集地', + '尼康器材党', + '绝不停止记录的2019', + '我要上“首页推荐位”', + '我要上开屏', + '分享神仙颜值', + '拍女友才是正经事' + ], + 'excerpt': '忙碌的圣诞季~', + 'favorite_list_prefix': [], + 'favorites': 37, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1924, + 'img_id': 204391728, + 'img_id_str': '204391728', + 'title': '001', + 'user_id': 2408598, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1280, + 'img_id': 144622598, + 'img_id_str': '144622598', + 'title': '001', + 'user_id': 2408598, + 'width': 1924 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1280, + 'img_id': 160941386, + 'img_id_str': '160941386', + 'title': '001', + 'user_id': 2408598, + 'width': 1924 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1280, + 'img_id': 373671086, + 'img_id_str': '373671086', + 'title': '001', + 'user_id': 2408598, + 'width': 1924 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1280, + 'img_id': 172147877, + 'img_id_str': '172147877', + 'title': '001', + 'user_id': 2408598, + 'width': 1924 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1280, + 'img_id': 394183699, + 'img_id_str': '394183699', + 'title': '001', + 'user_id': 2408598, + 'width': 1924 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1280, + 'img_id': 94029031, + 'img_id_str': '94029031', + 'title': '001', + 'user_id': 2408598, + 'width': 1924 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1924, + 'img_id': 175686845, + 'img_id_str': '175686845', + 'title': '001', + 'user_id': 2408598, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1924, + 'img_id': 93701664, + 'img_id_str': '93701664', + 'title': '001', + 'user_id': 2408598, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1280, + 'img_id': 427148654, + 'img_id_str': '427148654', + 'title': '001', + 'user_id': 2408598, + 'width': 1924 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1924, + 'img_id': 616678766, + 'img_id_str': '616678766', + 'title': '001', + 'user_id': 2408598, + 'width': 1280 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '2019年12月20日', + 'post_id': 60721603, + 'published_at': '2019-12-20 08:20:56', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 7436, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2408598_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '甄隐', + 'site_id': '2408598', + 'type': 'user', + 'url': 'https://tuchong.com/2408598/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2408598', + 'sites': [], + 'tags': [ + '上海人像圈子', + '上海大学摄影圈', + '图虫旅行摄影圈子', + '高颜值女神聚集地', + '尼康器材党', + '绝不停止记录的2019' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2408598/60721603/', + 'views': 2353 + }, + { + 'author_id': '15323144', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '妈妈带你飞!婆罗洲长臂猿母子,拍摄于万合地理刚刚结束的跨年的婆罗洲热带雨林行程。', + 'created_at': '2020-01-08 10:15:48', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '野生动物摄影与保护', + '河北摄影家俱乐部', + '在图虫看动物世界', + '江苏摄影俱乐部', + '浙江摄影俱乐部', + '广深摄影联盟', + '环球旅行摄影', + '上海交通大学摄影圈', + '图虫旅行摄影圈子', + '深圳摄影圈' + ], + 'excerpt': '妈妈带你飞!婆罗洲长臂猿母子,拍摄于万合地理刚刚结束的跨年的婆罗洲热带雨林行程。', + 'favorite_list_prefix': [], + 'favorites': 20, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2836, + 'img_id': 358729319, + 'img_id_str': '358729319', + 'title': '001', + 'user_id': 15323144, + 'width': 5043 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '01月08日', + 'post_id': 61510102, + 'published_at': '2020-01-08 10:15:48', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '2019年Nature’s Best Photography自然视频最佳荣誉奖获得者', + 'domain': '', + 'followers': 9974, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15323144_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '万合地理', + 'site_id': '15323144', + 'type': 'user', + 'url': 'https://tuchong.com/15323144/', + 'verification_list': [ + { + 'verification_reason': '2019年Nature’s Best Photography自然视频最佳荣誉奖获得者', + 'verification_type': 12 + } + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '15323144', + 'sites': [], + 'tags': [ + '野生动物摄影与保护', + '河北摄影家俱乐部', + '在图虫看动物世界', + '江苏摄影俱乐部', + '浙江摄影俱乐部', + '广深摄影联盟' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15323144/61510102/', + 'views': 497 + }, + { + 'author_id': '1654881', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '贝加尔湖的傍晚', + 'created_at': '2019-12-20 10:07:03', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['晒晒旅行打卡照'], + 'excerpt': '贝加尔湖的傍晚', + 'favorite_list_prefix': [], + 'favorites': 56, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 267895931, + 'img_id_str': '267895931', + 'title': '76300', + 'user_id': 1654881, + 'width': 6000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '4', + 'passed_time': '2019年12月20日', + 'post_id': 60724918, + 'published_at': '2019-12-20 10:07:03', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深旅行摄影师', + 'domain': 'gaoshushuphoto.tuchong.com', + 'followers': 3876, + 'has_everphoto_note': false, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1654881_7', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '高叔叔旅行摄影', + 'site_id': '1654881', + 'type': 'user', + 'url': 'https://gaoshushuphoto.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深旅行摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1654881', + 'sites': [], + 'tags': ['晒晒旅行打卡照', '旅行'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://gaoshushuphoto.tuchong.com/60724918/', + 'views': 2190 + }, + { + 'author_id': '373645', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': '冬季北极圈的正午,太阳短暂的升起后又即将落下,余晖中月亮清晰可见~', + 'created_at': '2020-01-05 14:51:15', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '冬季北极圈的正午,太阳短暂的升起后又即将落下,余晖中月亮清晰可见~', + 'favorite_list_prefix': [], + 'favorites': 51, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1201, + 'img_id': 39634649, + 'img_id_str': '39634649', + 'title': '', + 'user_id': 373645, + 'width': 1800 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月05日', + 'post_id': 61419304, + 'published_at': '2020-01-05 14:51:15', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '图虫图库销售达人', + 'domain': 'jiachenji.tuchong.com', + 'followers': 5579, + 'has_everphoto_note': false, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_373645_6', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '阿琛JiachenJi', + 'site_id': '373645', + 'type': 'user', + 'url': 'https://jiachenji.tuchong.com/', + 'verification_list': [ + {'verification_reason': '图虫图库销售达人', 'verification_type': 14} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '373645', + 'sites': [], + 'tags': ['风光', '色彩', '旅行', '日落', '夜景', '长曝光'], + 'title': '《正午的月》', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://jiachenji.tuchong.com/61419304/', + 'views': 1480 + }, + { + 'author_id': '1167303', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 11, + 'content': '没有刻意地见面\n\n就真的没有见过了/\n\n@摄影讲师李小龙', + 'created_at': '2019-11-25 16:21:17', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '没有刻意地见面\n\n就真的没有见过了/\n\n@摄影讲师李小龙', + 'favorite_list_prefix': [], + 'favorites': 231, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4044, + 'img_id': 419348211, + 'img_id_str': '419348211', + 'title': '', + 'user_id': 1167303, + 'width': 2696 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2976, + 'img_id': 333299830, + 'img_id_str': '333299830', + 'title': '', + 'user_id': 1167303, + 'width': 4464 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4323, + 'img_id': 513720581, + 'img_id_str': '513720581', + 'title': '', + 'user_id': 1167303, + 'width': 2882 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2878, + 'img_id': 385924927, + 'img_id_str': '385924927', + 'title': '', + 'user_id': 1167303, + 'width': 4317 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4290, + 'img_id': 256098005, + 'img_id_str': '256098005', + 'title': '', + 'user_id': 1167303, + 'width': 2860 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4227, + 'img_id': 101826311, + 'img_id_str': '101826311', + 'title': '', + 'user_id': 1167303, + 'width': 2818 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2804, + 'img_id': 106283225, + 'img_id_str': '106283225', + 'title': '', + 'user_id': 1167303, + 'width': 4206 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4183, + 'img_id': 86556889, + 'img_id_str': '86556889', + 'title': '', + 'user_id': 1167303, + 'width': 2789 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4209, + 'img_id': 424394895, + 'img_id_str': '424394895', + 'title': '', + 'user_id': 1167303, + 'width': 2806 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '10', + 'passed_time': '2019年11月25日', + 'post_id': 59315666, + 'published_at': '2019-11-25 16:21:17', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 8, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 9131, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1167303_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '摄影讲师李小龙', + 'site_id': '1167303', + 'type': 'user', + 'url': 'https://tuchong.com/1167303/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1167303', + 'sites': [], + 'tags': ['人像', '色彩', '情绪', '摄影讲师李小龙'], + 'title': '没有刻意地见面 就真的没有见过了/ @摄影讲师李小龙', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1167303/59315666/', + 'views': 21569 + }, + { + 'author_id': '1457161', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '', + 'created_at': '2019-12-26 22:41:12', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['2019你最满意的照片', '2020适马睛典'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 47, + 'image_count': 31, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 243451135, + 'img_id_str': '243451135', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 613467369, + 'img_id_str': '613467369', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 78366124, + 'img_id_str': '78366124', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 411026953, + 'img_id_str': '411026953', + 'title': '', + 'user_id': 1457161, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 39503365, + 'img_id_str': '39503365', + 'title': '', + 'user_id': 1457161, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 667, + 'img_id': 388024071, + 'img_id_str': '388024071', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 601671139, + 'img_id_str': '601671139', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 170837455, + 'img_id_str': '170837455', + 'title': '', + 'user_id': 1457161, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 117426055, + 'img_id_str': '117426055', + 'title': '', + 'user_id': 1457161, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 236569766, + 'img_id_str': '236569766', + 'title': '', + 'user_id': 1457161, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 648791371, + 'img_id_str': '648791371', + 'title': '', + 'user_id': 1457161, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1446, + 'img_id': 259769491, + 'img_id_str': '259769491', + 'title': '', + 'user_id': 1457161, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 108512253, + 'img_id_str': '108512253', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 667, + 'img_id': 204653811, + 'img_id_str': '204653811', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 425117373, + 'img_id_str': '425117373', + 'title': '', + 'user_id': 1457161, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1461, + 'img_id': 95274442, + 'img_id_str': '95274442', + 'title': '', + 'user_id': 1457161, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 295748782, + 'img_id_str': '295748782', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 601867762, + 'img_id_str': '601867762', + 'title': '', + 'user_id': 1457161, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 576308716, + 'img_id_str': '576308716', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 55887494, + 'img_id_str': '55887494', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 550946219, + 'img_id_str': '550946219', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 599180556, + 'img_id_str': '599180556', + 'title': '', + 'user_id': 1457161, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 354010553, + 'img_id_str': '354010553', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1548, + 'img_id': 408012170, + 'img_id_str': '408012170', + 'title': '', + 'user_id': 1457161, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 158582281, + 'img_id_str': '158582281', + 'title': '', + 'user_id': 1457161, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 74171694, + 'img_id_str': '74171694', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 667, + 'img_id': 197117327, + 'img_id_str': '197117327', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 351782042, + 'img_id_str': '351782042', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 565298718, + 'img_id_str': '565298718', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 329762116, + 'img_id_str': '329762116', + 'title': '', + 'user_id': 1457161, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 667, + 'img_id': 361153506, + 'img_id_str': '361153506', + 'title': '', + 'user_id': 1457161, + 'width': 1000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '2019年12月26日', + 'post_id': 61010097, + 'published_at': '2019-12-26 22:41:12', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深静物摄影师', + 'domain': 'lixiaojian00.tuchong.com', + 'followers': 1687, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1457161_4', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '李小简', + 'site_id': '1457161', + 'type': 'user', + 'url': 'https://lixiaojian00.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深静物摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1457161', + 'sites': [], + 'tags': ['2019你最满意的照片', '2020适马睛典', '少女', '人像', '胶片', '女孩'], + 'title': '《 游乐园 》', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://lixiaojian00.tuchong.com/61010097/', + 'views': 2323 + }, + { + 'author_id': '33937', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 95, + 'content': '志异书·羽见', + 'created_at': '2019-09-14 23:59:22', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['武汉人像摄影', '宜昌摄影小组', '华中农业大学', 'Alphastyle', '我要上开屏'], + 'excerpt': '志异书·羽见', + 'favorite_list_prefix': [], + 'favorites': 2263, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5094, + 'img_id': 397648555, + 'img_id_str': '397648555', + 'title': '001', + 'user_id': 33937, + 'width': 3396 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5486, + 'img_id': 391356992, + 'img_id_str': '391356992', + 'title': '001', + 'user_id': 33937, + 'width': 3658 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 393585472, + 'img_id_str': '393585472', + 'title': '001', + 'user_id': 33937, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5616, + 'img_id': 407478990, + 'img_id_str': '407478990', + 'title': '001', + 'user_id': 33937, + 'width': 3744 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4808, + 'img_id': 248554219, + 'img_id_str': '248554219', + 'title': '001', + 'user_id': 33937, + 'width': 3206 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5334, + 'img_id': 52929322, + 'img_id_str': '52929322', + 'title': '001', + 'user_id': 33937, + 'width': 3556 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2502, + 'img_id': 58827801, + 'img_id_str': '58827801', + 'title': '001', + 'user_id': 33937, + 'width': 3752 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 236888875, + 'img_id_str': '236888875', + 'title': '001', + 'user_id': 33937, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 437757072, + 'img_id_str': '437757072', + 'title': '001', + 'user_id': 33937, + 'width': 3840 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '77', + 'passed_time': '2019年09月14日', + 'post_id': 52625444, + 'published_at': '2019-09-14 23:59:22', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 58, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'rhine.tuchong.com', + 'followers': 3804, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_33937_13', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '时雨Rhyne', + 'site_id': '33937', + 'type': 'user', + 'url': 'https://rhine.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '33937', + 'sites': [], + 'tags': ['武汉人像摄影', '宜昌摄影小组', '华中农业大学', 'Alphastyle', '我要上开屏', '色彩'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://rhine.tuchong.com/52625444/', + 'views': 61504 + }, + { + 'author_id': '326444', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 19, + 'content': 'Wei', + 'created_at': '2020-01-15 11:28:52', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['人像写真精选'], + 'excerpt': 'Wei', + 'favorite_list_prefix': [], + 'favorites': 241, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 186238840, + 'img_id_str': '186238840', + 'title': '001', + 'user_id': 326444, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 97437401, + 'img_id_str': '97437401', + 'title': '001', + 'user_id': 326444, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 68536117, + 'img_id_str': '68536117', + 'title': '001', + 'user_id': 326444, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 520734392, + 'img_id_str': '520734392', + 'title': '001', + 'user_id': 326444, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 538560074, + 'img_id_str': '538560074', + 'title': '001', + 'user_id': 326444, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 565102028, + 'img_id_str': '565102028', + 'title': '001', + 'user_id': 326444, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 280086359, + 'img_id_str': '280086359', + 'title': '001', + 'user_id': 326444, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 230278901, + 'img_id_str': '230278901', + 'title': '001', + 'user_id': 326444, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 442222005, + 'img_id_str': '442222005', + 'title': '001', + 'user_id': 326444, + 'width': 1500 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '19', + 'passed_time': '01月15日', + 'post_id': 61725771, + 'published_at': '2020-01-15 11:28:52', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '7', + 'rqt_id': '', + 'shares': 10, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 3424, + 'has_everphoto_note': false, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_326444_3', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '羊扬杨', + 'site_id': '326444', + 'type': 'user', + 'url': 'https://tuchong.com/326444/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '326444', + 'sites': [], + 'tags': ['人像写真精选', '床', '卧室', '女孩', '人像', '写真'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/326444/61725771/', + 'views': 17899 + }, + { + 'author_id': '2976763', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 67, + 'content': 'Photographer:顾小白Hala\nModel:猫病\nOrganizer:文珺Ronnie', + 'created_at': '2019-11-26 16:55:16', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '高颜值女神聚集地', + '上海人像圈子', + '糖水人像小组', + '人像爱好者', + '绝不停止记录的2019', + '分享神仙颜值', + '暖色调人像' + ], + 'excerpt': 'Photographer:顾小白Hala\nModel:猫病\nOrganizer:文珺Ronnie', + 'favorite_list_prefix': [], + 'favorites': 3097, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 7472, + 'img_id': 58441791, + 'img_id_str': '58441791', + 'title': '', + 'user_id': 2976763, + 'width': 4983 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7270, + 'img_id': 181977193, + 'img_id_str': '181977193', + 'title': '', + 'user_id': 2976763, + 'width': 4850 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7376, + 'img_id': 275628276, + 'img_id_str': '275628276', + 'title': '', + 'user_id': 2976763, + 'width': 4920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 209436826, + 'img_id_str': '209436826', + 'title': '', + 'user_id': 2976763, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 457424979, + 'img_id_str': '457424979', + 'title': '', + 'user_id': 2976763, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7525, + 'img_id': 382910660, + 'img_id_str': '382910660', + 'title': '', + 'user_id': 2976763, + 'width': 5019 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7535, + 'img_id': 91733984, + 'img_id_str': '91733984', + 'title': '', + 'user_id': 2976763, + 'width': 5026 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6932, + 'img_id': 557432970, + 'img_id_str': '557432970', + 'title': '', + 'user_id': 2976763, + 'width': 4624 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7160, + 'img_id': 178045317, + 'img_id_str': '178045317', + 'title': '', + 'user_id': 2976763, + 'width': 4776 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 94617967, + 'img_id_str': '94617967', + 'title': '', + 'user_id': 2976763, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 205700718, + 'img_id_str': '205700718', + 'title': '', + 'user_id': 2976763, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 350666874, + 'img_id_str': '350666874', + 'title': '', + 'user_id': 2976763, + 'width': 5304 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '60', + 'passed_time': '2019年11月26日', + 'post_id': 59381354, + 'published_at': '2019-11-26 16:55:16', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 74, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'gxbhala.tuchong.com', + 'followers': 39077, + 'has_everphoto_note': false, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2976763_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '顾小白Hala', + 'site_id': '2976763', + 'type': 'user', + 'url': 'https://gxbhala.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2976763', + 'sites': [], + 'tags': [ + '高颜值女神聚集地', + '上海人像圈子', + '糖水人像小组', + '人像爱好者', + '绝不停止记录的2019', + '分享神仙颜值' + ], + 'title': '七月的风八月的雨', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://gxbhala.tuchong.com/59381354/', + 'views': 93517 + }, + { + 'author_id': '1413882', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 12, + 'content': 'Girl and toy', + 'created_at': '2019-11-26 09:29:35', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['写真人像摄影', '成都约拍圈子', '成都人像爱好者', '高颜值女神聚集地', '分享神仙颜值'], + 'excerpt': 'Girl and toy', + 'favorite_list_prefix': [], + 'favorites': 205, + 'image_count': 3, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3092, + 'img_id': 297451301, + 'img_id_str': '297451301', + 'title': '001', + 'user_id': 1413882, + 'width': 4638 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 344375776, + 'img_id_str': '344375776', + 'title': '001', + 'user_id': 1413882, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3823, + 'img_id': 489734451, + 'img_id_str': '489734451', + 'title': '001', + 'user_id': 1413882, + 'width': 5734 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '11', + 'passed_time': '2019年11月26日', + 'post_id': 59359309, + 'published_at': '2019-11-26 09:29:35', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 2548, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1413882_3', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'Batkid', + 'site_id': '1413882', + 'type': 'user', + 'url': 'https://tuchong.com/1413882/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1413882', + 'sites': [], + 'tags': ['写真人像摄影', '成都约拍圈子', '成都人像爱好者', '高颜值女神聚集地', '分享神仙颜值', '人像'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1413882/59359309/', + 'views': 12689 + }, + { + 'author_id': '1167303', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 26, + 'content': 'flowers/\n摄影:@摄影讲师李小龙', + 'created_at': '2019-11-24 09:13:34', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': 'flowers/\n摄影:@摄影讲师李小龙', + 'favorite_list_prefix': [], + 'favorites': 554, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 381927278, + 'img_id_str': '381927278', + 'title': '', + 'user_id': 1167303, + 'width': 2666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 591970623, + 'img_id_str': '591970623', + 'title': '', + 'user_id': 1167303, + 'width': 2666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 219529401, + 'img_id_str': '219529401', + 'title': '', + 'user_id': 1167303, + 'width': 2666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2666, + 'img_id': 561692553, + 'img_id_str': '561692553', + 'title': '', + 'user_id': 1167303, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 553107480, + 'img_id_str': '553107480', + 'title': '', + 'user_id': 1167303, + 'width': 2666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 312066273, + 'img_id_str': '312066273', + 'title': '', + 'user_id': 1167303, + 'width': 2666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 607830143, + 'img_id_str': '607830143', + 'title': '', + 'user_id': 1167303, + 'width': 2666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 135839640, + 'img_id_str': '135839640', + 'title': '', + 'user_id': 1167303, + 'width': 2666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5333, + 'img_id': 584433655, + 'img_id_str': '584433655', + 'title': '', + 'user_id': 1167303, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2666, + 'img_id': 478003022, + 'img_id_str': '478003022', + 'title': '', + 'user_id': 1167303, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 151764971, + 'img_id_str': '151764971', + 'title': '', + 'user_id': 1167303, + 'width': 2666 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '26', + 'passed_time': '2019年11月24日', + 'post_id': 59209950, + 'published_at': '2019-11-24 09:13:34', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '10', + 'rqt_id': '', + 'shares': 22, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 9131, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1167303_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '摄影讲师李小龙', + 'site_id': '1167303', + 'type': 'user', + 'url': 'https://tuchong.com/1167303/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1167303', + 'sites': [], + 'tags': ['人像', '色彩', '写真', '唯美', '武汉摄影', '摄影讲师李小龙'], + 'title': 'flowers/ @摄影讲师李小龙', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1167303/59209950/', + 'views': 18940 + }, + { + 'author_id': '1878099', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 14, + 'content': '#喜欢秋天?\n 连打喷嚏的时候\n 都会变成"爱秋"~\n\n ???', + 'created_at': '2019-10-20 16:00:30', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '#喜欢秋天?\n 连打喷嚏的时候\n 都会变成"爱秋"~\n\n ???', + 'favorite_list_prefix': [], + 'favorites': 317, + 'image_count': 8, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 43562163, + 'img_id_str': '43562163', + 'title': '001', + 'user_id': 1878099, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 89372283, + 'img_id_str': '89372283', + 'title': '001', + 'user_id': 1878099, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3557, + 'img_id': 217167268, + 'img_id_str': '217167268', + 'title': '001', + 'user_id': 1878099, + 'width': 5336 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 484226447, + 'img_id_str': '484226447', + 'title': '001', + 'user_id': 1878099, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1984, + 'img_id': 518042896, + 'img_id_str': '518042896', + 'title': '001', + 'user_id': 1878099, + 'width': 1323 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 564573467, + 'img_id_str': '564573467', + 'title': '001', + 'user_id': 1878099, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3166, + 'img_id': 411415776, + 'img_id_str': '411415776', + 'title': '001', + 'user_id': 1878099, + 'width': 4749 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 451130560, + 'img_id_str': '451130560', + 'title': '001', + 'user_id': 1878099, + 'width': 3840 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '14', + 'passed_time': '2019年10月20日', + 'post_id': 56451282, + 'published_at': '2019-10-20 16:00:30', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 6, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 461, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1878099_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '阿俊的家常摄影', + 'site_id': '1878099', + 'type': 'user', + 'url': 'https://tuchong.com/1878099/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1878099', + 'sites': [], + 'tags': ['浴室少女', '洗澡'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1878099/56451282/', + 'views': 16518 + }, + { + 'author_id': '8080241', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 5, + 'content': '体操服少女『上篇』\n\n摄于深大,下篇将会去到户外体育场,敬请期待', + 'created_at': '2020-01-15 16:20:18', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['Alphastyle', '高颜值女神聚集地', '糖水人像小组'], + 'excerpt': '体操服少女『上篇』\n\n摄于深大,下篇将会去到户外体育场,敬请期待', + 'favorite_list_prefix': [], + 'favorites': 259, + 'image_count': 13, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3375, + 'img_id': 244631432, + 'img_id_str': '244631432', + 'title': '001', + 'user_id': 8080241, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 136562376, + 'img_id_str': '136562376', + 'title': '001', + 'user_id': 8080241, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 534825029, + 'img_id_str': '534825029', + 'title': '001', + 'user_id': 8080241, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 445826467, + 'img_id_str': '445826467', + 'title': '001', + 'user_id': 8080241, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 536528189, + 'img_id_str': '536528189', + 'title': '001', + 'user_id': 8080241, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 151701271, + 'img_id_str': '151701271', + 'title': '001', + 'user_id': 8080241, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 160876358, + 'img_id_str': '160876358', + 'title': '001', + 'user_id': 8080241, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 483968653, + 'img_id_str': '483968653', + 'title': '001', + 'user_id': 8080241, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 593479525, + 'img_id_str': '593479525', + 'title': '001', + 'user_id': 8080241, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 328451914, + 'img_id_str': '328451914', + 'title': '001', + 'user_id': 8080241, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 206620333, + 'img_id_str': '206620333', + 'title': '001', + 'user_id': 8080241, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 183420535, + 'img_id_str': '183420535', + 'title': '001', + 'user_id': 8080241, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 300599215, + 'img_id_str': '300599215', + 'title': '001', + 'user_id': 8080241, + 'width': 2000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '4', + 'passed_time': '01月15日', + 'post_id': 61732092, + 'published_at': '2020-01-15 16:20:18', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 2263, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_8080241_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '黄轩wower', + 'site_id': '8080241', + 'type': 'user', + 'url': 'https://tuchong.com/8080241/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '8080241', + 'sites': [], + 'tags': ['Alphastyle', '高颜值女神聚集地', '糖水人像小组', '体操服', '少女', '写真'], + 'title': '体操服少女『上篇』『室内篇』', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/8080241/61732092/', + 'views': 12653 + }, + { + 'author_id': '3062259', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': '', + 'created_at': '2020-01-07 22:36:49', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['毛茸茸的小动物'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 23, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3909, + 'img_id': 610125360, + 'img_id_str': '610125360', + 'title': '001', + 'user_id': 3062259, + 'width': 5864 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月07日', + 'post_id': 61499223, + 'published_at': '2020-01-07 22:36:49', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深宠物摄影师', + 'domain': '', + 'followers': 855, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3062259_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '布约二筒摄影工作室', + 'site_id': '3062259', + 'type': 'user', + 'url': 'https://tuchong.com/3062259/', + 'verification_list': [ + {'verification_reason': '资深宠物摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3062259', + 'sites': [], + 'tags': ['毛茸茸的小动物', '狗', '犬科', '宠物', '可爱', '滑稽'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3062259/61499223/', + 'views': 962 + }, + { + 'author_id': '1937912', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 13, + 'content': '摄影:紫荞\n模特:CC\n地址:小鹿奔跑了摄影工作室\n—一组少女室内写真-', + 'created_at': '2019-11-07 10:02:25', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['图虫·OPENSEE2019'], + 'excerpt': '摄影:紫荞\n模特:CC\n地址:小鹿奔跑了摄影工作室\n—一组少女室内写真-', + 'favorite_list_prefix': [], + 'favorites': 458, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 551860724, + 'img_id_str': '551860724', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 285522968, + 'img_id_str': '285522968', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3980, + 'img_id': 610515614, + 'img_id_str': '610515614', + 'title': '001', + 'user_id': 1937912, + 'width': 5851 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 272349957, + 'img_id_str': '272349957', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 51821070, + 'img_id_str': '51821070', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6068, + 'img_id': 191478595, + 'img_id_str': '191478595', + 'title': '001', + 'user_id': 1937912, + 'width': 4035 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6172, + 'img_id': 332315518, + 'img_id_str': '332315518', + 'title': '001', + 'user_id': 1937912, + 'width': 4075 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 446741124, + 'img_id_str': '446741124', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5911, + 'img_id': 102546487, + 'img_id_str': '102546487', + 'title': '001', + 'user_id': 1937912, + 'width': 4043 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '13', + 'passed_time': '2019年11月07日', + 'post_id': 57990560, + 'published_at': '2019-11-07 10:02:25', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 10, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 7144, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1937912_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '紫荞姑娘', + 'site_id': '1937912', + 'type': 'user', + 'url': 'https://tuchong.com/1937912/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1937912', + 'sites': [], + 'tags': ['图虫·OPENSEE2019', '在室内', '人像', '深圳约拍', '时光是个美人', '文艺小清新'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1937912/57990560/', + 'views': 16320 + }, + { + 'author_id': '1724834', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '“ 愿有人陪你疯陪你傻 陪你成熟直到长大 ”\n\n? 丨 @漫三摄影工作室', + 'created_at': '2019-12-19 17:02:01', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['高颜值女神聚集地', '带我看看,你的城市', '我要上开屏', '趣味话题:唯美如你'], + 'excerpt': '“ 愿有人陪你疯陪你傻 陪你成熟直到长大 ”\n\n? 丨 @漫三摄影工作室', + 'favorite_list_prefix': [], + 'favorites': 38, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1080, + 'img_id': 314164137, + 'img_id_str': '314164137', + 'title': '001', + 'user_id': 1724834, + 'width': 1620 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1080, + 'img_id': 644137804, + 'img_id_str': '644137804', + 'title': '001', + 'user_id': 1724834, + 'width': 1620 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1080, + 'img_id': 547275898, + 'img_id_str': '547275898', + 'title': '001', + 'user_id': 1724834, + 'width': 1620 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1080, + 'img_id': 425640946, + 'img_id_str': '425640946', + 'title': '001', + 'user_id': 1724834, + 'width': 1620 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1080, + 'img_id': 639485093, + 'img_id_str': '639485093', + 'title': '001', + 'user_id': 1724834, + 'width': 1620 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1080, + 'img_id': 483312457, + 'img_id_str': '483312457', + 'title': '001', + 'user_id': 1724834, + 'width': 1620 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 408011927, + 'img_id_str': '408011927', + 'title': '001', + 'user_id': 1724834, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 522896403, + 'img_id_str': '522896403', + 'title': '001', + 'user_id': 1724834, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 200459305, + 'img_id_str': '200459305', + 'title': '001', + 'user_id': 1724834, + 'width': 1080 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '2019年12月19日', + 'post_id': 60696153, + 'published_at': '2019-12-19 17:02:01', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 2, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 1356, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1724834_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '漫三摄影工作室', + 'site_id': '1724834', + 'type': 'user', + 'url': 'https://tuchong.com/1724834/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1724834', + 'sites': [], + 'tags': ['高颜值女神聚集地', '带我看看,你的城市', '我要上开屏', '趣味话题:唯美如你'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1724834/60696153/', + 'views': 2134 + }, + { + 'author_id': '1182492', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 63, + 'content': '有你的避风港', + 'created_at': '2019-10-07 13:12:24', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['人像摄影集', '分享神仙颜值'], + 'excerpt': '有你的避风港', + 'favorite_list_prefix': [], + 'favorites': 2051, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 110538869, + 'img_id_str': '110538869', + 'title': '001', + 'user_id': 1182492, + 'width': 3375 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 506572916, + 'img_id_str': '506572916', + 'title': '001', + 'user_id': 1182492, + 'width': 3780 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 519745408, + 'img_id_str': '519745408', + 'title': '001', + 'user_id': 1182492, + 'width': 3375 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 109096819, + 'img_id_str': '109096819', + 'title': '001', + 'user_id': 1182492, + 'width': 3375 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 612740712, + 'img_id_str': '612740712', + 'title': '001', + 'user_id': 1182492, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 324382323, + 'img_id_str': '324382323', + 'title': '001', + 'user_id': 1182492, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 466202652, + 'img_id_str': '466202652', + 'title': '001', + 'user_id': 1182492, + 'width': 3375 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 108179491, + 'img_id_str': '108179491', + 'title': '001', + 'user_id': 1182492, + 'width': 3780 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 562933975, + 'img_id_str': '562933975', + 'title': '001', + 'user_id': 1182492, + 'width': 3780 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '60', + 'passed_time': '2019年10月07日', + 'post_id': 55083333, + 'published_at': '2019-10-07 13:12:24', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 52, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 13242, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1182492_6', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'KINGVISION', + 'site_id': '1182492', + 'type': 'user', + 'url': 'https://tuchong.com/1182492/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1182492', + 'sites': [], + 'tags': ['人像摄影集', '分享神仙颜值', '人像', '少女写真', '在室内'], + 'title': '有你的避风港', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1182492/55083333/', + 'views': 78437 + }, + { + 'author_id': '1182492', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 19, + 'content': '放学后一起去看银杏', + 'created_at': '2019-12-10 01:14:08', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['高颜值女神聚集地', '人像摄影集', '分享神仙颜值', '我们都爱日系摄影'], + 'excerpt': '放学后一起去看银杏', + 'favorite_list_prefix': [], + 'favorites': 385, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 646825098, + 'img_id_str': '646825098', + 'title': '001', + 'user_id': 1182492, + 'width': 3375 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 377537285, + 'img_id_str': '377537285', + 'title': '001', + 'user_id': 1182492, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 379503513, + 'img_id_str': '379503513', + 'title': '001', + 'user_id': 1182492, + 'width': 3375 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 607175504, + 'img_id_str': '607175504', + 'title': '001', + 'user_id': 1182492, + 'width': 3375 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 428655253, + 'img_id_str': '428655253', + 'title': '001', + 'user_id': 1182492, + 'width': 3375 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 33932015, + 'img_id_str': '33932015', + 'title': '001', + 'user_id': 1182492, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 171623852, + 'img_id_str': '171623852', + 'title': '001', + 'user_id': 1182492, + 'width': 3375 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 240174086, + 'img_id_str': '240174086', + 'title': '001', + 'user_id': 1182492, + 'width': 3375 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 245088936, + 'img_id_str': '245088936', + 'title': '001', + 'user_id': 1182492, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 60671065, + 'img_id_str': '60671065', + 'title': '001', + 'user_id': 1182492, + 'width': 3375 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '19', + 'passed_time': '2019年12月10日', + 'post_id': 60190047, + 'published_at': '2019-12-10 01:14:08', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 18, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 13242, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1182492_6', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'KINGVISION', + 'site_id': '1182492', + 'type': 'user', + 'url': 'https://tuchong.com/1182492/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1182492', + 'sites': [], + 'tags': ['高颜值女神聚集地', '人像摄影集', '分享神仙颜值', '我们都爱日系摄影', '银杏', 'JK'], + 'title': '放学后一起去看银杏', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1182492/60190047/', + 'views': 11610 + }, + { + 'author_id': '1785951', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 33, + 'content': '', + 'created_at': '2019-11-15 22:16:39', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 794, + 'image_count': 13, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 477740566, + 'img_id_str': '477740566', + 'title': '001', + 'user_id': 1785951, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 524467616, + 'img_id_str': '524467616', + 'title': '001', + 'user_id': 1785951, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 232374269, + 'img_id_str': '232374269', + 'title': '001', + 'user_id': 1785951, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 654425499, + 'img_id_str': '654425499', + 'title': '001', + 'user_id': 1785951, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 529579538, + 'img_id_str': '529579538', + 'title': '001', + 'user_id': 1785951, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 251313460, + 'img_id_str': '251313460', + 'title': '001', + 'user_id': 1785951, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 439467689, + 'img_id_str': '439467689', + 'title': '001', + 'user_id': 1785951, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 283426327, + 'img_id_str': '283426327', + 'title': '001', + 'user_id': 1785951, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 382778549, + 'img_id_str': '382778549', + 'title': '001', + 'user_id': 1785951, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 321568079, + 'img_id_str': '321568079', + 'title': '001', + 'user_id': 1785951, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 94878979, + 'img_id_str': '94878979', + 'title': '001', + 'user_id': 1785951, + 'width': 2460 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 356040094, + 'img_id_str': '356040094', + 'title': '001', + 'user_id': 1785951, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 149339692, + 'img_id_str': '149339692', + 'title': '001', + 'user_id': 1785951, + 'width': 3648 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '30', + 'passed_time': '2019年11月15日', + 'post_id': 58622763, + 'published_at': '2019-11-15 22:16:39', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 9, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 934, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1785951_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '苏冬至', + 'site_id': '1785951', + 'type': 'user', + 'url': 'https://tuchong.com/1785951/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1785951', + 'sites': [], + 'tags': [], + 'title': '“热爱世间万物,无最爱,无例外”', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1785951/58622763/', + 'views': 49073 + }, + { + 'author_id': '15728263', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': null, + 'created_at': '2019-12-15 09:35:40', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['文艺私房圈子', '美女人像群', '人像摄影集', '写真人像摄影', '暖色调人像'], + 'excerpt': + '1.模特,是让摄影师拍的对象,但是,模特是有血有肉的人,所以要考虑她的内心想法和感受,不能说摄影师想怎么拍就怎么拍,事先肯定要交流沟通的2.确定好了拍摄的主题,定好时间地点去完成拍摄,期间要有耐心的去引导模特,让彼此都能处于愉快的工作拍摄状态3.拍摄期间注意休息,休息时候可以一起看拍摄出来的照片,及时沟通调整4.模特自己喜欢的照片,希望摄影师能分享给她', + 'favorite_list_prefix': [], + 'favorites': 2, + 'image_count': 0, + 'images': [], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '4', + 'passed_time': '2019年12月15日', + 'post_id': 60471782, + 'published_at': '2019-12-15 09:35:40', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 0, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15728263_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '友成茗茶老板娘', + 'site_id': '15728263', + 'type': 'user', + 'url': 'https://tuchong.com/15728263/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15728263', + 'sites': [], + 'tags': ['文艺私房圈子', '美女人像群', '人像摄影集', '写真人像摄影', '暖色调人像'], + 'title': '摄影模特眼里的摄影师都有哪些类型?模特喜欢和什么样的摄影师长期合作呢?', + 'title_image': null, + 'type': 'text', + 'update': false, + 'url': 'https://tuchong.com/15728263/t/60471782/', + 'views': 920 + }, + { + 'author_id': '5417230', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '天外飞仙,黄山仙境', + 'created_at': '2020-01-18 18:41:21', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['最满意的风光照'], + 'excerpt': '天外飞仙,黄山仙境', + 'favorite_list_prefix': [], + 'favorites': 15, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4740, + 'img_id': 425117771, + 'img_id_str': '425117771', + 'title': '001', + 'user_id': 5417230, + 'width': 6822 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '01月18日', + 'post_id': 61817452, + 'published_at': '2020-01-18 18:41:21', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 173, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_5417230_5', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'Franky-Wang', + 'site_id': '5417230', + 'type': 'user', + 'url': 'https://tuchong.com/5417230/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '5417230', + 'sites': [], + 'tags': ['最满意的风光照', '山', '旅行', '雪', '风景优美的', '岩石'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/5417230/61817452/', + 'views': 550 + }, + { + 'author_id': '2976763', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 51, + 'content': 'Photographer:顾小白Hala\nModel:木木\nOrganizer:文珺Ronnie', + 'created_at': '2020-01-05 16:15:15', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': 'Photographer:顾小白Hala\nModel:木木\nOrganizer:文珺Ronnie', + 'favorite_list_prefix': [], + 'favorites': 1090, + 'image_count': 14, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 271304520, + 'img_id_str': '271304520', + 'title': '', + 'user_id': 2976763, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5890, + 'img_id': 607373428, + 'img_id_str': '607373428', + 'title': '', + 'user_id': 2976763, + 'width': 3926 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 330286271, + 'img_id_str': '330286271', + 'title': '', + 'user_id': 2976763, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 261408236, + 'img_id_str': '261408236', + 'title': '', + 'user_id': 2976763, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 61720558, + 'img_id_str': '61720558', + 'title': '', + 'user_id': 2976763, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 495240899, + 'img_id_str': '495240899', + 'title': '', + 'user_id': 2976763, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 439666298, + 'img_id_str': '439666298', + 'title': '', + 'user_id': 2976763, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5922, + 'img_id': 490391266, + 'img_id_str': '490391266', + 'title': '', + 'user_id': 2976763, + 'width': 3948 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 528598547, + 'img_id_str': '528598547', + 'title': '', + 'user_id': 2976763, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 147703357, + 'img_id_str': '147703357', + 'title': '', + 'user_id': 2976763, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6424, + 'img_id': 212321628, + 'img_id_str': '212321628', + 'title': '', + 'user_id': 2976763, + 'width': 4283 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4136, + 'img_id': 272090926, + 'img_id_str': '272090926', + 'title': '', + 'user_id': 2976763, + 'width': 6204 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 149537798, + 'img_id_str': '149537798', + 'title': '', + 'user_id': 2976763, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 55035638, + 'img_id_str': '55035638', + 'title': '', + 'user_id': 2976763, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '46', + 'passed_time': '01月05日', + 'post_id': 61421507, + 'published_at': '2020-01-05 16:15:15', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 31, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'gxbhala.tuchong.com', + 'followers': 39077, + 'has_everphoto_note': false, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2976763_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '顾小白Hala', + 'site_id': '2976763', + 'type': 'user', + 'url': 'https://gxbhala.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2976763', + 'sites': [], + 'tags': ['人像', '佳能', '小清新', '美女', '女孩', '少女'], + 'title': '二十岁的某一天', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://gxbhala.tuchong.com/61421507/', + 'views': 47936 + }, + { + 'author_id': '1117387', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 61, + 'content': '拥有你和猫咪,就是最幸福的事~', + 'created_at': '2019-11-27 14:42:22', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['半厨分享', '分享神仙颜值'], + 'excerpt': '拥有你和猫咪,就是最幸福的事~', + 'favorite_list_prefix': [], + 'favorites': 1134, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1819, + 'img_id': 109625297, + 'img_id_str': '109625297', + 'title': '001', + 'user_id': 1117387, + 'width': 1233 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1829, + 'img_id': 289259645, + 'img_id_str': '289259645', + 'title': '001', + 'user_id': 1117387, + 'width': 1242 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1826, + 'img_id': 653508779, + 'img_id_str': '653508779', + 'title': '001', + 'user_id': 1117387, + 'width': 1211 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1768, + 'img_id': 285786071, + 'img_id_str': '285786071', + 'title': '001', + 'user_id': 1117387, + 'width': 1242 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1793, + 'img_id': 124501713, + 'img_id_str': '124501713', + 'title': '001', + 'user_id': 1117387, + 'width': 1208 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1805, + 'img_id': 648593575, + 'img_id_str': '648593575', + 'title': '001', + 'user_id': 1117387, + 'width': 1229 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1828, + 'img_id': 513589103, + 'img_id_str': '513589103', + 'title': '001', + 'user_id': 1117387, + 'width': 1242 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1759, + 'img_id': 378715961, + 'img_id_str': '378715961', + 'title': '001', + 'user_id': 1117387, + 'width': 1230 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1515, + 'img_id': 430227509, + 'img_id_str': '430227509', + 'title': '001', + 'user_id': 1117387, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1789, + 'img_id': 59818462, + 'img_id_str': '59818462', + 'title': '001', + 'user_id': 1117387, + 'width': 1240 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '55', + 'passed_time': '2019年11月27日', + 'post_id': 59435437, + 'published_at': '2019-11-27 14:42:22', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '33', + 'rqt_id': '', + 'shares': 55, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 6224, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1117387_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '陈半厨', + 'site_id': '1117387', + 'type': 'user', + 'url': 'https://tuchong.com/1117387/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1117387', + 'sites': [], + 'tags': ['半厨分享', '分享神仙颜值', '人像', '美女', '小清新', '佳能'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1117387/59435437/', + 'views': 55736 + }, + { + 'author_id': '999799', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 32, + 'content': '出镜:@GAMA伽马', + 'created_at': '2019-10-09 15:06:01', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '出镜:@GAMA伽马', + 'favorite_list_prefix': [], + 'favorites': 1195, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 233681205, + 'img_id_str': '233681205', + 'title': '', + 'user_id': 999799, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5417, + 'img_id': 125087680, + 'img_id_str': '125087680', + 'title': '', + 'user_id': 999799, + 'width': 3616 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5625, + 'img_id': 587706414, + 'img_id_str': '587706414', + 'title': '', + 'user_id': 999799, + 'width': 3755 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 340963716, + 'img_id_str': '340963716', + 'title': '', + 'user_id': 999799, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 73642374, + 'img_id_str': '73642374', + 'title': '', + 'user_id': 999799, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 539734245, + 'img_id_str': '539734245', + 'title': '', + 'user_id': 999799, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5107, + 'img_id': 297054517, + 'img_id_str': '297054517', + 'title': '', + 'user_id': 999799, + 'width': 3409 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5569, + 'img_id': 460501234, + 'img_id_str': '460501234', + 'title': '', + 'user_id': 999799, + 'width': 3717 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 248557656, + 'img_id_str': '248557656', + 'title': '', + 'user_id': 999799, + 'width': 4016 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '28', + 'passed_time': '2019年10月09日', + 'post_id': 55358786, + 'published_at': '2019-10-09 15:06:01', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 31, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 6436, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_999799_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '歌罢', + 'site_id': '999799', + 'type': 'user', + 'url': 'https://tuchong.com/999799/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '999799', + 'sites': [], + 'tags': ['JK', '少女', '人像', '日系', '情绪', '尼康'], + 'title': 'JK制服美少女', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/999799/55358786/', + 'views': 48325 + }, + { + 'author_id': '8261035', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '金色望京城。', + 'created_at': '2020-01-07 09:44:52', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '环球旅行摄影', + '中国爬楼联盟圈子', + '城市·夜晚', + '每人推荐一个旅行目的地', + '街拍纪实手册', + '这张天空必须要分享', + '最满意的风光照', + '风光人文地理', + '行摄部落', + '带我看看,你的城市' + ], + 'excerpt': '金色望京城。', + 'favorite_list_prefix': [], + 'favorites': 53, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3543, + 'img_id': 121160770, + 'img_id_str': '121160770', + 'title': '001', + 'user_id': 8261035, + 'width': 5315 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '01月07日', + 'post_id': 61477943, + 'published_at': '2020-01-07 09:44:52', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 1, + 'site': { + 'description': '', + 'domain': '', + 'followers': 156, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_8261035_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'aotu7', + 'site_id': '8261035', + 'type': 'user', + 'url': 'https://tuchong.com/8261035/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '8261035', + 'sites': [], + 'tags': [ + '环球旅行摄影', + '中国爬楼联盟圈子', + '城市·夜晚', + '每人推荐一个旅行目的地', + '街拍纪实手册', + '这张天空必须要分享' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/8261035/61477943/', + 'views': 1885 + }, + { + 'author_id': '458707', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 97, + 'content': '', + 'created_at': '2019-09-16 17:22:25', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 2691, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1334, + 'img_id': 450404980, + 'img_id_str': '450404980', + 'title': '', + 'user_id': 458707, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 451257409, + 'img_id_str': '451257409', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 182428566, + 'img_id_str': '182428566', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 414164208, + 'img_id_str': '414164208', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 513254262, + 'img_id_str': '513254262', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 142647887, + 'img_id_str': '142647887', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 130721194, + 'img_id_str': '130721194', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 294298547, + 'img_id_str': '294298547', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 578004279, + 'img_id_str': '578004279', + 'title': '', + 'user_id': 458707, + 'width': 1334 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '85', + 'passed_time': '2019年09月16日', + 'post_id': 52771050, + 'published_at': '2019-09-16 17:22:25', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 93, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 8735, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_458707_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '青焱', + 'site_id': '458707', + 'type': 'user', + 'url': 'https://tuchong.com/458707/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '458707', + 'sites': [], + 'tags': ['人像'], + 'title': '肖像', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/458707/52771050/', + 'views': 154323 + }, + { + 'author_id': '1790780', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 45, + 'content': '摩卡摩卡巧克力', + 'created_at': '2019-10-01 17:56:48', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '摩卡摩卡巧克力', + 'favorite_list_prefix': [], + 'favorites': 1328, + 'image_count': 14, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 480684592, + 'img_id_str': '480684592', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2999, + 'img_id': 247704251, + 'img_id_str': '247704251', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 197438488, + 'img_id_str': '197438488', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 361606110, + 'img_id_str': '361606110', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 429828736, + 'img_id_str': '429828736', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 265399596, + 'img_id_str': '265399596', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 652782167, + 'img_id_str': '652782167', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 517253831, + 'img_id_str': '517253831', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3001, + 'img_id': 646229285, + 'img_id_str': '646229285', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 365734200, + 'img_id_str': '365734200', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6552, + 'img_id': 628992869, + 'img_id_str': '628992869', + 'title': '001', + 'user_id': 1790780, + 'width': 4368 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 261204599, + 'img_id_str': '261204599', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 646425309, + 'img_id_str': '646425309', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 125480100, + 'img_id_str': '125480100', + 'title': '001', + 'user_id': 1790780, + 'width': 2000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '42', + 'passed_time': '2019年10月01日', + 'post_id': 54166249, + 'published_at': '2019-10-01 17:56:48', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 54, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 4991, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1790780_7', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '萌琦琦M77', + 'site_id': '1790780', + 'type': 'user', + 'url': 'https://tuchong.com/1790780/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1790780', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1790780/54166249/', + 'views': 51924 + }, + { + 'author_id': '1366787', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 66, + 'content': + '这组片子有些地方借鉴了一些lwasong,delfinacarmona,iamwinter的元素\n希望以后更厉害了能吸收得了大师们的东西,也希望能有更多属于我自己的元素', + 'created_at': '2019-07-23 13:05:40', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['2019华为新影像', '2019上海公益创作年展', 'ien姥姥摄影课堂'], + 'excerpt': + '这组片子有些地方借鉴了一些lwasong,delfinacarmona,iamwinter的元素\n希望以后更厉害了能吸收得了大师们的东西,也希望能有更多属于我自己的元素', + 'favorite_list_prefix': [], + 'favorites': 2340, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3426, + 'img_id': 530479509, + 'img_id_str': '530479509', + 'title': '', + 'user_id': 1366787, + 'width': 5140 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3748, + 'img_id': 309164504, + 'img_id_str': '309164504', + 'title': '', + 'user_id': 1366787, + 'width': 5622 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3379, + 'img_id': 116423049, + 'img_id_str': '116423049', + 'title': '', + 'user_id': 1366787, + 'width': 3379 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4493, + 'img_id': 271022625, + 'img_id_str': '271022625', + 'title': '', + 'user_id': 1366787, + 'width': 4494 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 178944511, + 'img_id_str': '178944511', + 'title': '', + 'user_id': 1366787, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5620, + 'img_id': 478313131, + 'img_id_str': '478313131', + 'title': '', + 'user_id': 1366787, + 'width': 3747 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3139, + 'img_id': 57965372, + 'img_id_str': '57965372', + 'title': '', + 'user_id': 1366787, + 'width': 5362 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3384, + 'img_id': 135232091, + 'img_id_str': '135232091', + 'title': '', + 'user_id': 1366787, + 'width': 5561 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3235, + 'img_id': 79723045, + 'img_id_str': '79723045', + 'title': '', + 'user_id': 1366787, + 'width': 3235 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '62', + 'passed_time': '2019年07月23日', + 'post_id': 45833869, + 'published_at': '2019-07-23 13:05:40', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 63, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 3372, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1366787_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'ien姥姥', + 'site_id': '1366787', + 'type': 'user', + 'url': 'https://tuchong.com/1366787/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1366787', + 'sites': [], + 'tags': ['2019华为新影像', '2019上海公益创作年展', 'ien姥姥摄影课堂', '人像', '情绪', '观念'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1366787/45833869/', + 'views': 94286 + }, + { + 'author_id': '1561136', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '', + 'created_at': '2020-01-13 15:52:04', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['闪迪旅拍大赛', '2019你最满意的照片'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 32, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 874, + 'img_id': 238404981, + 'img_id_str': '238404981', + 'title': '', + 'user_id': 1561136, + 'width': 1400 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '01月13日', + 'post_id': 61672334, + 'published_at': '2020-01-13 15:52:04', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 2, + 'site': { + 'description': + '披星戴月,踏浪冒雪,寒来暑往,风雨兼程。主拍大连风光。摄影QQ:664512936,微信:manlan123456', + 'domain': 'yuhe.tuchong.com', + 'followers': 452, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1561136_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '雨禾9', + 'site_id': '1561136', + 'type': 'user', + 'url': 'https://yuhe.tuchong.com/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '1561136', + 'sites': [], + 'tags': ['闪迪旅拍大赛', '2019你最满意的照片', '风光', '人像', '旅行', '尼康'], + 'title': '童话雪域', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://yuhe.tuchong.com/61672334/', + 'views': 1516 + }, + { + 'author_id': '2677003', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 72, + 'content': '《定格瞬间的情绪》喜欢街拍的朋友,欢迎加入我的圈子【街拍纪实手册】', + 'created_at': '2019-12-16 20:16:34', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '街拍纪实手册', + '火烛一花精品摄影', + '街头拍客', + '街头人物圈子', + '街事圈子', + '我爱街拍摄影小组圈子', + '人文纪实手册', + '我要上“首页推荐位”', + '绝不停止记录的2019', + '行摄部落' + ], + 'excerpt': '《定格瞬间的情绪》喜欢街拍的朋友,欢迎加入我的圈子【街拍纪实手册】', + 'favorite_list_prefix': [], + 'favorites': 324, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 317571737, + 'img_id_str': '317571737', + 'title': '579425', + 'user_id': 2677003, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 545178388, + 'img_id_str': '545178388', + 'title': '579427', + 'user_id': 2677003, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 360694509, + 'img_id_str': '360694509', + 'title': '579429', + 'user_id': 2677003, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 512410271, + 'img_id_str': '512410271', + 'title': '579426', + 'user_id': 2677003, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 649249586, + 'img_id_str': '649249586', + 'title': '579431', + 'user_id': 2677003, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 497927403, + 'img_id_str': '497927403', + 'title': '579433', + 'user_id': 2677003, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 363447089, + 'img_id_str': '363447089', + 'title': '579428', + 'user_id': 2677003, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 388613170, + 'img_id_str': '388613170', + 'title': '579430', + 'user_id': 2677003, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 456508578, + 'img_id_str': '456508578', + 'title': '579434', + 'user_id': 2677003, + 'width': 2000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '49', + 'passed_time': '2019年12月16日', + 'post_id': 60556903, + 'published_at': '2019-12-16 20:16:34', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 16, + 'site': { + 'description': '资深纪实摄影师', + 'domain': 'gewala81.tuchong.com', + 'followers': 4103, + 'has_everphoto_note': false, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2677003_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '格瓦拉81', + 'site_id': '2677003', + 'type': 'user', + 'url': 'https://gewala81.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深纪实摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2677003', + 'sites': [], + 'tags': ['街拍纪实手册', '火烛一花精品摄影', '街头拍客', '街头人物圈子', '街事圈子', '我爱街拍摄影小组圈子'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://gewala81.tuchong.com/60556903/', + 'views': 8500 + }, + { + 'author_id': '1807584', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 23, + 'content': '', + 'created_at': '2019-08-09 22:23:40', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['分享神仙颜值'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 761, + 'image_count': 16, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 36014793, + 'img_id_str': '36014793', + 'title': '', + 'user_id': 1807584, + 'width': 3235 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2144, + 'img_id': 372541799, + 'img_id_str': '372541799', + 'title': '', + 'user_id': 1807584, + 'width': 1725 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3065, + 'img_id': 96832604, + 'img_id_str': '96832604', + 'title': '', + 'user_id': 1807584, + 'width': 2189 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2839, + 'img_id': 599034818, + 'img_id_str': '599034818', + 'title': '', + 'user_id': 1807584, + 'width': 2182 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3288, + 'img_id': 87853716, + 'img_id_str': '87853716', + 'title': '', + 'user_id': 1807584, + 'width': 2202 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3256, + 'img_id': 266177203, + 'img_id_str': '266177203', + 'title': '', + 'user_id': 1807584, + 'width': 2208 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3132, + 'img_id': 149195244, + 'img_id_str': '149195244', + 'title': '', + 'user_id': 1807584, + 'width': 2191 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3140, + 'img_id': 427985410, + 'img_id_str': '427985410', + 'title': '', + 'user_id': 1807584, + 'width': 2076 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2196, + 'img_id': 614304383, + 'img_id_str': '614304383', + 'title': '', + 'user_id': 1807584, + 'width': 3245 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3262, + 'img_id': 274041679, + 'img_id_str': '274041679', + 'title': '', + 'user_id': 1807584, + 'width': 2195 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2057, + 'img_id': 236489331, + 'img_id_str': '236489331', + 'title': '', + 'user_id': 1807584, + 'width': 3152 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1011, + 'img_id': 177965893, + 'img_id_str': '177965893', + 'title': '', + 'user_id': 1807584, + 'width': 1481 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3271, + 'img_id': 115772073, + 'img_id_str': '115772073', + 'title': '', + 'user_id': 1807584, + 'width': 2198 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3117, + 'img_id': 140675600, + 'img_id_str': '140675600', + 'title': '', + 'user_id': 1807584, + 'width': 2198 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3259, + 'img_id': 521374232, + 'img_id_str': '521374232', + 'title': '', + 'user_id': 1807584, + 'width': 2196 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3257, + 'img_id': 484936484, + 'img_id_str': '484936484', + 'title': '', + 'user_id': 1807584, + 'width': 2187 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '23', + 'passed_time': '2019年08月09日', + 'post_id': 48453784, + 'published_at': '2019-08-09 22:23:40', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 15, + 'site': { + 'description': '独立摄影师,参与「夏日短歌——图虫·OPEN MUJI摄影俳句展」', + 'domain': 'huenjs.tuchong.com', + 'followers': 5458, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1807584_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'huenjs', + 'site_id': '1807584', + 'type': 'user', + 'url': 'https://huenjs.tuchong.com/', + 'verification_list': [ + { + 'verification_reason': '独立摄影师,参与「夏日短歌——图虫·OPEN MUJI摄影俳句展」', + 'verification_type': 12 + } + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1807584', + 'sites': [], + 'tags': ['分享神仙颜值', '风光', '人像', '胶片', '杭州约拍'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://huenjs.tuchong.com/48453784/', + 'views': 29844 + }, + { + 'author_id': '4171230', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': + '早安呦,今日份早餐一人食滴卡\n感恩每一天的早餐,无论是阴天,还是晴天,它都如期而至,不将就的生活,要从不将就的心态开始。今日份早餐 柠檬三文鱼+南瓜+西葫芦+蘑菇+彩椒,手冲咖啡。喜欢的草莓杯到了,这个年可以好好过了,果然剁手最开心', + 'created_at': '2020-01-14 09:56:14', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': + '早安呦,今日份早餐一人食滴卡\n感恩每一天的早餐,无论是阴天,还是晴天,它都如期而至,不将就的生活,要从不将就的心态开始。今日份早餐 柠檬三文鱼+南瓜+西葫芦+蘑菇+彩椒,手冲咖啡。喜欢的草莓杯到了,这个年可以好好过了,果然剁手最开心', + 'favorite_list_prefix': [], + 'favorites': 33, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 445499315, + 'img_id_str': '445499315', + 'title': '1059443', + 'user_id': 4171230, + 'width': 4928 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4928, + 'img_id': 274384726, + 'img_id_str': '274384726', + 'title': '1059442', + 'user_id': 4171230, + 'width': 3264 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 399099057, + 'img_id_str': '399099057', + 'title': '1059441', + 'user_id': 4171230, + 'width': 4928 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 358729419, + 'img_id_str': '358729419', + 'title': '1059440', + 'user_id': 4171230, + 'width': 4928 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4928, + 'img_id': 55887855, + 'img_id_str': '55887855', + 'title': '1059439', + 'user_id': 4171230, + 'width': 3264 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4928, + 'img_id': 411092844, + 'img_id_str': '411092844', + 'title': '1059438', + 'user_id': 4171230, + 'width': 3264 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4928, + 'img_id': 99796628, + 'img_id_str': '99796628', + 'title': '1059437', + 'user_id': 4171230, + 'width': 3264 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4928, + 'img_id': 356632377, + 'img_id_str': '356632377', + 'title': '1059436', + 'user_id': 4171230, + 'width': 3264 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 205768205, + 'img_id_str': '205768205', + 'title': '1059435', + 'user_id': 4171230, + 'width': 4928 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月14日', + 'post_id': 61693769, + 'published_at': '2020-01-14 09:56:14', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深静物摄影师', + 'domain': '', + 'followers': 2207, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_4171230_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'cynthia-shan', + 'site_id': '4171230', + 'type': 'user', + 'url': 'https://tuchong.com/4171230/', + 'verification_list': [ + {'verification_reason': '资深静物摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '4171230', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/4171230/61693769/', + 'views': 1154 + }, + { + 'author_id': '1896147', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '图虫影像历,小欢喜。', + 'created_at': '2020-01-02 20:36:15', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['取景器里的小美好', '日系静物摄影圈子', '静物美学', '生活小确幸', '我要上开屏'], + 'excerpt': '图虫影像历,小欢喜。', + 'favorite_list_prefix': [], + 'favorites': 40, + 'image_count': 5, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 228377624, + 'img_id_str': '228377624', + 'title': '3194871', + 'user_id': 1896147, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1688, + 'img_id': 59229710, + 'img_id_str': '59229710', + 'title': '3194873', + 'user_id': 1896147, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 335528879, + 'img_id_str': '335528879', + 'title': '3194872', + 'user_id': 1896147, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 568379280, + 'img_id_str': '568379280', + 'title': '3194870', + 'user_id': 1896147, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 376031010, + 'img_id_str': '376031010', + 'title': '3194869', + 'user_id': 1896147, + 'width': 6720 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月02日', + 'post_id': 61331733, + 'published_at': '2020-01-02 20:36:15', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深静物摄影师', + 'domain': '', + 'followers': 4738, + 'has_everphoto_note': false, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1896147_9', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '韩逍子', + 'site_id': '1896147', + 'type': 'user', + 'url': 'https://tuchong.com/1896147/', + 'verification_list': [ + {'verification_reason': '资深静物摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1896147', + 'sites': [], + 'tags': ['取景器里的小美好', '日系静物摄影圈子', '静物美学', '生活小确幸', '我要上开屏'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1896147/61331733/', + 'views': 2102 + }, + { + 'author_id': '2378810', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '傣族人民结婚的时候都喜欢用自己的民族服装做嫁衣,对于她们来说傣装等同于白纱一样重要。\n你们呢喜欢什么民族。', + 'created_at': '2020-01-17 17:48:10', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '傣族人民结婚的时候都喜欢用自己的民族服装做嫁衣,对于她们来说傣装等同于白纱一样重要。\n你们呢喜欢什么民族。', + 'favorite_list_prefix': [], + 'favorites': 29, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1200, + 'img_id': 294701214, + 'img_id_str': '294701214', + 'title': '001', + 'user_id': 2378810, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 610780710, + 'img_id_str': '610780710', + 'title': '001', + 'user_id': 2378810, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1200, + 'img_id': 144558014, + 'img_id_str': '144558014', + 'title': '001', + 'user_id': 2378810, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 641386218, + 'img_id_str': '641386218', + 'title': '001', + 'user_id': 2378810, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1200, + 'img_id': 408929786, + 'img_id_str': '408929786', + 'title': '001', + 'user_id': 2378810, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 228050924, + 'img_id_str': '228050924', + 'title': '001', + 'user_id': 2378810, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 356042471, + 'img_id_str': '356042471', + 'title': '001', + 'user_id': 2378810, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 373933911, + 'img_id_str': '373933911', + 'title': '001', + 'user_id': 2378810, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 94684297, + 'img_id_str': '94684297', + 'title': '001', + 'user_id': 2378810, + 'width': 1200 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '01月17日', + 'post_id': 61789312, + 'published_at': '2020-01-17 17:48:10', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 1304, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2378810_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '赵无敌丿', + 'site_id': '2378810', + 'type': 'user', + 'url': 'https://tuchong.com/2378810/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2378810', + 'sites': [], + 'tags': ['民族'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2378810/61789312/', + 'views': 1342 + }, + { + 'author_id': '3052006', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '辛巴', + 'created_at': '2019-12-23 09:19:50', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['图虫旅行摄影圈子', '在图虫看动物世界', '捕捉最萌瞬间'], + 'excerpt': '辛巴', + 'favorite_list_prefix': [], + 'favorites': 23, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2274, + 'img_id': 378258834, + 'img_id_str': '378258834', + 'title': '001', + 'user_id': 3052006, + 'width': 4096 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '2019年12月23日', + 'post_id': 60863273, + 'published_at': '2019-12-23 09:19:50', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 2, + 'site': { + 'description': '资深旅行摄影师', + 'domain': '', + 'followers': 1016, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3052006_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '六一纪录', + 'site_id': '3052006', + 'type': 'user', + 'url': 'https://tuchong.com/3052006/', + 'verification_list': [ + {'verification_reason': '资深旅行摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3052006', + 'sites': [], + 'tags': ['图虫旅行摄影圈子', '在图虫看动物世界', '捕捉最萌瞬间', '唐怀瑟·电影滤镜', '纪实', '非洲'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3052006/60863273/', + 'views': 1321 + }, + { + 'author_id': '1207473', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 0, + 'content': '《念》\n爱上你恋上这座城市,人海里第一眼就认出你', + 'created_at': '2020-01-07 22:11:53', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['富士Fujifilm圈子', '人像爱好者', '约拍长沙'], + 'excerpt': '《念》\n爱上你恋上这座城市,人海里第一眼就认出你', + 'favorite_list_prefix': [], + 'favorites': 18, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 557500028, + 'img_id_str': '557500028', + 'title': '78992', + 'user_id': 1207473, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4787, + 'img_id': 502449778, + 'img_id_str': '502449778', + 'title': '78994', + 'user_id': 1207473, + 'width': 3191 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 71419509, + 'img_id_str': '71419509', + 'title': '78993', + 'user_id': 1207473, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 488818312, + 'img_id_str': '488818312', + 'title': '78991', + 'user_id': 1207473, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 635750086, + 'img_id_str': '635750086', + 'title': '78988', + 'user_id': 1207473, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 456509179, + 'img_id_str': '456509179', + 'title': '78995', + 'user_id': 1207473, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5495, + 'img_id': 347457887, + 'img_id_str': '347457887', + 'title': '78987', + 'user_id': 1207473, + 'width': 3663 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 237815311, + 'img_id_str': '237815311', + 'title': '78989', + 'user_id': 1207473, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 511756026, + 'img_id_str': '511756026', + 'title': '78990', + 'user_id': 1207473, + 'width': 6000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '0', + 'passed_time': '01月07日', + 'post_id': 61498305, + 'published_at': '2020-01-07 22:11:53', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 1260, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1207473_3', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '平凡爱拍照玩儿', + 'site_id': '1207473', + 'type': 'user', + 'url': 'https://tuchong.com/1207473/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1207473', + 'sites': [], + 'tags': ['富士Fujifilm圈子', '人像爱好者', '约拍长沙', '人像', '街拍', '城市的'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1207473/61498305/', + 'views': 1219 + }, + { + 'author_id': '5588814', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 33, + 'content': '', + 'created_at': '2019-09-29 15:01:13', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 772, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 666, + 'img_id': 441624905, + 'img_id_str': '441624905', + 'title': '', + 'user_id': 5588814, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 667, + 'img_id': 376874906, + 'img_id_str': '376874906', + 'title': '', + 'user_id': 5588814, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 667, + 'img_id': 108243981, + 'img_id_str': '108243981', + 'title': '', + 'user_id': 5588814, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 285715060, + 'img_id_str': '285715060', + 'title': '', + 'user_id': 5588814, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 586853077, + 'img_id_str': '586853077', + 'title': '', + 'user_id': 5588814, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 667, + 'img_id': 260942614, + 'img_id_str': '260942614', + 'title': '', + 'user_id': 5588814, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 667, + 'img_id': 478587499, + 'img_id_str': '478587499', + 'title': '', + 'user_id': 5588814, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 191211464, + 'img_id_str': '191211464', + 'title': '', + 'user_id': 5588814, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 467905176, + 'img_id_str': '467905176', + 'title': '', + 'user_id': 5588814, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 667, + 'img_id': 282437912, + 'img_id_str': '282437912', + 'title': '', + 'user_id': 5588814, + 'width': 1000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '28', + 'passed_time': '2019年09月29日', + 'post_id': 53912573, + 'published_at': '2019-09-29 15:01:13', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 16, + 'site': { + 'description': '义乌专业淘宝商业摄影 ,视频制作,详情页设计', + 'domain': '', + 'followers': 1844, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_5588814_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '义乌金刚摄影', + 'site_id': '5588814', + 'type': 'user', + 'url': 'https://tuchong.com/5588814/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '5588814', + 'sites': [], + 'tags': ['人像', '电商摄影', '淘宝摄'], + 'title': '保暖衣拍摄', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/5588814/53912573/', + 'views': 58714 + }, + { + 'author_id': '912745', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 18, + 'content': '夏至已至', + 'created_at': '2019-09-26 01:24:32', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['广深摄影联盟'], + 'excerpt': '夏至已至', + 'favorite_list_prefix': [], + 'favorites': 397, + 'image_count': 29, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 400729988, + 'img_id_str': '400729988', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3431, + 'img_id': 385984564, + 'img_id_str': '385984564', + 'title': '001', + 'user_id': 912745, + 'width': 2295 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 259106640, + 'img_id_str': '259106640', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 490317805, + 'img_id_str': '490317805', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 228370131, + 'img_id_str': '228370131', + 'title': '001', + 'user_id': 912745, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 280602985, + 'img_id_str': '280602985', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 409249592, + 'img_id_str': '409249592', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 247637893, + 'img_id_str': '247637893', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 508274785, + 'img_id_str': '508274785', + 'title': '001', + 'user_id': 912745, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 528197548, + 'img_id_str': '528197548', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 541501230, + 'img_id_str': '541501230', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3348, + 'img_id': 269134075, + 'img_id_str': '269134075', + 'title': '001', + 'user_id': 912745, + 'width': 2240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 290039482, + 'img_id_str': '290039482', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 532719703, + 'img_id_str': '532719703', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 235317387, + 'img_id_str': '235317387', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 441624538, + 'img_id_str': '441624538', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 525379422, + 'img_id_str': '525379422', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 451716777, + 'img_id_str': '451716777', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 289319271, + 'img_id_str': '289319271', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 355641204, + 'img_id_str': '355641204', + 'title': '001', + 'user_id': 912745, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 107391052, + 'img_id_str': '107391052', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 519874359, + 'img_id_str': '519874359', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 566404842, + 'img_id_str': '566404842', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 243378172, + 'img_id_str': '243378172', + 'title': '001', + 'user_id': 912745, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 143501154, + 'img_id_str': '143501154', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 540977155, + 'img_id_str': '540977155', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 175876229, + 'img_id_str': '175876229', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3429, + 'img_id': 529639379, + 'img_id_str': '529639379', + 'title': '001', + 'user_id': 912745, + 'width': 2294 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 170436642, + 'img_id_str': '170436642', + 'title': '001', + 'user_id': 912745, + 'width': 2433 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '16', + 'passed_time': '2019年09月26日', + 'post_id': 53586675, + 'published_at': '2019-09-26 01:24:32', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 15, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 790, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_912745_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '张晨婷', + 'site_id': '912745', + 'type': 'user', + 'url': 'https://tuchong.com/912745/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '912745', + 'sites': [], + 'tags': ['广深摄影联盟', '人像', '女孩', '日系写真', '胶片'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/912745/53586675/', + 'views': 16334 + }, + { + 'author_id': '2668748', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 26, + 'content': '阴天', + 'created_at': '2019-08-06 07:47:43', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['郑州职业技术学院摄影圈'], + 'excerpt': '阴天', + 'favorite_list_prefix': [], + 'favorites': 464, + 'image_count': 30, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 157779691, + 'img_id_str': '157779691', + 'title': '1180698', + 'user_id': 2668748, + 'width': 3358 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 308905823, + 'img_id_str': '308905823', + 'title': '1180700', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 173246244, + 'img_id_str': '173246244', + 'title': '1180699', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 306153008, + 'img_id_str': '306153008', + 'title': '1180696', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 636651315, + 'img_id_str': '636651315', + 'title': '1180695', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 321292194, + 'img_id_str': '321292194', + 'title': '1180701', + 'user_id': 2668748, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 615155491, + 'img_id_str': '615155491', + 'title': '1180702', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 311527515, + 'img_id_str': '311527515', + 'title': '1180703', + 'user_id': 2668748, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 602965691, + 'img_id_str': '602965691', + 'title': '1180704', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 340559577, + 'img_id_str': '340559577', + 'title': '1180705', + 'user_id': 2668748, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 110594219, + 'img_id_str': '110594219', + 'title': '1180706', + 'user_id': 2668748, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3413, + 'img_id': 258770978, + 'img_id_str': '258770978', + 'title': '1180707', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 55543740, + 'img_id_str': '55543740', + 'title': '1180708', + 'user_id': 2668748, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3413, + 'img_id': 594380340, + 'img_id_str': '594380340', + 'title': '1180709', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 605980507, + 'img_id_str': '605980507', + 'title': '1180694', + 'user_id': 2668748, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 318408277, + 'img_id_str': '318408277', + 'title': '1180693', + 'user_id': 2668748, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1525, + 'img_id': 148998080, + 'img_id_str': '148998080', + 'title': '1180690', + 'user_id': 2668748, + 'width': 2712 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 461211504, + 'img_id_str': '461211504', + 'title': '1180691', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 385976190, + 'img_id_str': '385976190', + 'title': '1180692', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 113215492, + 'img_id_str': '113215492', + 'title': '1180689', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 310412940, + 'img_id_str': '310412940', + 'title': '1180688', + 'user_id': 2668748, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3413, + 'img_id': 544769710, + 'img_id_str': '544769710', + 'title': '1180687', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5428, + 'img_id': 605718106, + 'img_id_str': '605718106', + 'title': '1180686', + 'user_id': 2668748, + 'width': 3349 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2879, + 'img_id': 215189237, + 'img_id_str': '215189237', + 'title': '1180676', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 349013902, + 'img_id_str': '349013902', + 'title': '1180677', + 'user_id': 2668748, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 234129786, + 'img_id_str': '234129786', + 'title': '1180675', + 'user_id': 2668748, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 136546111, + 'img_id_str': '136546111', + 'title': '1180674', + 'user_id': 2668748, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 445154885, + 'img_id_str': '445154885', + 'title': '1180671', + 'user_id': 2668748, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 332367852, + 'img_id_str': '332367852', + 'title': '1180672', + 'user_id': 2668748, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 258312261, + 'img_id_str': '258312261', + 'title': '1180673', + 'user_id': 2668748, + 'width': 5120 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '14', + 'passed_time': '2019年08月06日', + 'post_id': 47874715, + 'published_at': '2019-08-06 07:47:43', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 12, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 1828, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2668748_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影师Linn', + 'site_id': '2668748', + 'type': 'user', + 'url': 'https://tuchong.com/2668748/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2668748', + 'sites': [], + 'tags': ['郑州职业技术学院摄影圈', '女人', '人像', '色彩'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2668748/47874715/', + 'views': 19422 + }, + { + 'author_id': '3258736', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 3, + 'content': '乘叮叮车沿途城市景观记录。', + 'created_at': '2019-12-17 11:14:46', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '乘叮叮车沿途城市景观记录。', + 'favorite_list_prefix': [], + 'favorites': 46, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1688, + 'img_id': 177653009, + 'img_id_str': '177653009', + 'title': '', + 'user_id': 3258736, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 640074932, + 'img_id_str': '640074932', + 'title': '', + 'user_id': 3258736, + 'width': 1688 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1687, + 'img_id': 629130345, + 'img_id_str': '629130345', + 'title': '', + 'user_id': 3258736, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1688, + 'img_id': 331596784, + 'img_id_str': '331596784', + 'title': '', + 'user_id': 3258736, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1687, + 'img_id': 326288601, + 'img_id_str': '326288601', + 'title': '', + 'user_id': 3258736, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1688, + 'img_id': 487965985, + 'img_id_str': '487965985', + 'title': '', + 'user_id': 3258736, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1688, + 'img_id': 533054761, + 'img_id_str': '533054761', + 'title': '', + 'user_id': 3258736, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1688, + 'img_id': 548848661, + 'img_id_str': '548848661', + 'title': '', + 'user_id': 3258736, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1688, + 'img_id': 303482045, + 'img_id_str': '303482045', + 'title': '', + 'user_id': 3258736, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1688, + 'img_id': 281461505, + 'img_id_str': '281461505', + 'title': '', + 'user_id': 3258736, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1688, + 'img_id': 57590567, + 'img_id_str': '57590567', + 'title': '', + 'user_id': 3258736, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1688, + 'img_id': 52086132, + 'img_id_str': '52086132', + 'title': '', + 'user_id': 3258736, + 'width': 3000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '2019年12月17日', + 'post_id': 60585063, + 'published_at': '2019-12-17 11:14:46', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 6, + 'site': { + 'description': '资深旅行摄影师', + 'domain': '', + 'followers': 1462, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3258736_6', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'mau5', + 'site_id': '3258736', + 'type': 'user', + 'url': 'https://tuchong.com/3258736/', + 'verification_list': [ + {'verification_reason': '资深旅行摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3258736', + 'sites': [], + 'tags': ['香港', '叮叮车', '港岛', '电车', '交通', '人文'], + 'title': '叮叮车合集', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3258736/60585063/', + 'views': 2688 + }, + { + 'author_id': '1108701', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 99, + 'content': '', + 'created_at': '2019-11-01 10:22:55', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['瓦特摄影学院'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1534, + 'image_count': 8, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 238991249, + 'img_id_str': '238991249', + 'title': '', + 'user_id': 1108701, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5363, + 'img_id': 262453812, + 'img_id_str': '262453812', + 'title': '', + 'user_id': 1108701, + 'width': 3576 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 472627522, + 'img_id_str': '472627522', + 'title': '', + 'user_id': 1108701, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5561, + 'img_id': 300268348, + 'img_id_str': '300268348', + 'title': '', + 'user_id': 1108701, + 'width': 3707 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5661, + 'img_id': 483637397, + 'img_id_str': '483637397', + 'title': '', + 'user_id': 1108701, + 'width': 3774 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 521844960, + 'img_id_str': '521844960', + 'title': '', + 'user_id': 1108701, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 315341314, + 'img_id_str': '315341314', + 'title': '', + 'user_id': 1108701, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 189184471, + 'img_id_str': '189184471', + 'title': '', + 'user_id': 1108701, + 'width': 3840 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '94', + 'passed_time': '2019年11月01日', + 'post_id': 57469920, + 'published_at': '2019-11-01 10:22:55', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 129, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 5141, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1108701_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '浅溪先生', + 'site_id': '1108701', + 'type': 'user', + 'url': 'https://tuchong.com/1108701/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1108701', + 'sites': [], + 'tags': ['瓦特摄影学院', '人像', '汉服', '古风', '汉服摄影'], + 'title': '霜降', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1108701/57469920/', + 'views': 42937 + }, + { + 'author_id': '1604446', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 60, + 'content': '同一天的周而复始,若不在哪里留下折痕,说不定会产生错觉。', + 'created_at': '2019-11-07 13:00:33', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['我要上“首页推荐位”'], + 'excerpt': '同一天的周而复始,若不在哪里留下折痕,说不定会产生错觉。', + 'favorite_list_prefix': [], + 'favorites': 985, + 'image_count': 3, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 277592920, + 'img_id_str': '277592920', + 'title': '001', + 'user_id': 1604446, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 563723584, + 'img_id_str': '563723584', + 'title': '001', + 'user_id': 1604446, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 141605939, + 'img_id_str': '141605939', + 'title': '001', + 'user_id': 1604446, + 'width': 3840 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '52', + 'passed_time': '2019年11月07日', + 'post_id': 58001231, + 'published_at': '2019-11-07 13:00:33', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 33, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'zmsilent.tuchong.com', + 'followers': 6471, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1604446_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '瓦子卡', + 'site_id': '1604446', + 'type': 'user', + 'url': 'https://zmsilent.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1604446', + 'sites': [], + 'tags': ['我要上“首页推荐位”', '人像', '北京约拍', '女孩'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://zmsilent.tuchong.com/58001231/', + 'views': 32607 + }, + { + 'author_id': '1001036', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 29, + 'content': '摄影&后期:@会拍照的咔咔\n模特:@嘿是你的牛奶', + 'created_at': '2019-10-11 12:20:41', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '摄影&后期:@会拍照的咔咔\n模特:@嘿是你的牛奶', + 'favorite_list_prefix': [], + 'favorites': 870, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 316190905, + 'img_id_str': '316190905', + 'title': '', + 'user_id': 1001036, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 113684678, + 'img_id_str': '113684678', + 'title': '', + 'user_id': 1001036, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1000, + 'img_id': 395751551, + 'img_id_str': '395751551', + 'title': '', + 'user_id': 1001036, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 192917690, + 'img_id_str': '192917690', + 'title': '', + 'user_id': 1001036, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 289255788, + 'img_id_str': '289255788', + 'title': '', + 'user_id': 1001036, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 311472224, + 'img_id_str': '311472224', + 'title': '', + 'user_id': 1001036, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 476492564, + 'img_id_str': '476492564', + 'title': '', + 'user_id': 1001036, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 286830697, + 'img_id_str': '286830697', + 'title': '', + 'user_id': 1001036, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 263106689, + 'img_id_str': '263106689', + 'title': '', + 'user_id': 1001036, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1000, + 'img_id': 134394952, + 'img_id_str': '134394952', + 'title': '', + 'user_id': 1001036, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 96973146, + 'img_id_str': '96973146', + 'title': '', + 'user_id': 1001036, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 132624781, + 'img_id_str': '132624781', + 'title': '', + 'user_id': 1001036, + 'width': 1000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '28', + 'passed_time': '2019年10月11日', + 'post_id': 55555111, + 'published_at': '2019-10-11 12:20:41', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '11', + 'rqt_id': '', + 'shares': 22, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 12656, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1001036_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '会拍照的咔咔', + 'site_id': '1001036', + 'type': 'user', + 'url': 'https://tuchong.com/1001036/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1001036', + 'sites': [], + 'tags': ['人像', '写真', '日系', '小清新'], + 'title': '嘿是你的牛奶', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1001036/55555111/', + 'views': 37706 + }, + { + 'author_id': '5265270', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 45, + 'content': '秋日里的绿。。。', + 'created_at': '2019-10-15 20:44:15', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '秋日里的绿。。。', + 'favorite_list_prefix': [], + 'favorites': 734, + 'image_count': 15, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2304, + 'img_id': 569094813, + 'img_id_str': '569094813', + 'title': '001', + 'user_id': 5265270, + 'width': 1536 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2304, + 'img_id': 226014274, + 'img_id_str': '226014274', + 'title': '001', + 'user_id': 5265270, + 'width': 1536 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1536, + 'img_id': 581874524, + 'img_id_str': '581874524', + 'title': '001', + 'user_id': 5265270, + 'width': 2304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2304, + 'img_id': 361870141, + 'img_id_str': '361870141', + 'title': '001', + 'user_id': 5265270, + 'width': 1536 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1536, + 'img_id': 537178914, + 'img_id_str': '537178914', + 'title': '001', + 'user_id': 5265270, + 'width': 2060 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1536, + 'img_id': 555070310, + 'img_id_str': '555070310', + 'title': '001', + 'user_id': 5265270, + 'width': 2304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2302, + 'img_id': 57979870, + 'img_id_str': '57979870', + 'title': '001', + 'user_id': 5265270, + 'width': 1536 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2304, + 'img_id': 474067925, + 'img_id_str': '474067925', + 'title': '001', + 'user_id': 5265270, + 'width': 1536 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2304, + 'img_id': 530821931, + 'img_id_str': '530821931', + 'title': '001', + 'user_id': 5265270, + 'width': 1536 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2304, + 'img_id': 430748301, + 'img_id_str': '430748301', + 'title': '001', + 'user_id': 5265270, + 'width': 1536 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2048, + 'img_id': 235320253, + 'img_id_str': '235320253', + 'title': '001', + 'user_id': 5265270, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2304, + 'img_id': 413905535, + 'img_id_str': '413905535', + 'title': '001', + 'user_id': 5265270, + 'width': 1536 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1536, + 'img_id': 556511895, + 'img_id_str': '556511895', + 'title': '001', + 'user_id': 5265270, + 'width': 2304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2304, + 'img_id': 176665329, + 'img_id_str': '176665329', + 'title': '001', + 'user_id': 5265270, + 'width': 1536 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2304, + 'img_id': 288600810, + 'img_id_str': '288600810', + 'title': '001', + 'user_id': 5265270, + 'width': 1536 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '45', + 'passed_time': '2019年10月15日', + 'post_id': 55990626, + 'published_at': '2019-10-15 20:44:15', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 31, + 'site': { + 'description': '资深儿童摄影师', + 'domain': '', + 'followers': 4251, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_5265270_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '林小玲LIN00', + 'site_id': '5265270', + 'type': 'user', + 'url': 'https://tuchong.com/5265270/', + 'verification_list': [ + {'verification_reason': '资深儿童摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '5265270', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/5265270/55990626/', + 'views': 28302 + }, + { + 'author_id': '1401628', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 43, + 'content': '不问归期\n出镜:@老鼠窜过雨棚 \n拍摄:@青子wj', + 'created_at': '2019-12-05 23:24:27', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '不问归期\n出镜:@老鼠窜过雨棚 \n拍摄:@青子wj', + 'favorite_list_prefix': [], + 'favorites': 693, + 'image_count': 18, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 287163380, + 'img_id_str': '287163380', + 'title': '', + 'user_id': 1401628, + 'width': 5168 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3181, + 'img_id': 394379932, + 'img_id_str': '394379932', + 'title': '', + 'user_id': 1401628, + 'width': 4563 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 51167630, + 'img_id_str': '51167630', + 'title': '', + 'user_id': 1401628, + 'width': 4982 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 153797162, + 'img_id_str': '153797162', + 'title': '', + 'user_id': 1401628, + 'width': 5168 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 87671601, + 'img_id_str': '87671601', + 'title': '', + 'user_id': 1401628, + 'width': 5168 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2496, + 'img_id': 628998760, + 'img_id_str': '628998760', + 'title': '', + 'user_id': 1401628, + 'width': 2783 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 277987480, + 'img_id_str': '277987480', + 'title': '', + 'user_id': 1401628, + 'width': 5168 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3339, + 'img_id': 58310923, + 'img_id_str': '58310923', + 'title': '', + 'user_id': 1401628, + 'width': 4262 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4918, + 'img_id': 458604528, + 'img_id_str': '458604528', + 'title': '', + 'user_id': 1401628, + 'width': 3281 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 336118361, + 'img_id_str': '336118361', + 'title': '', + 'user_id': 1401628, + 'width': 4592 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 57590015, + 'img_id_str': '57590015', + 'title': '', + 'user_id': 1401628, + 'width': 5168 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2973, + 'img_id': 170639681, + 'img_id_str': '170639681', + 'title': '', + 'user_id': 1401628, + 'width': 4451 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3370, + 'img_id': 72467000, + 'img_id_str': '72467000', + 'title': '', + 'user_id': 1401628, + 'width': 2660 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 46383430, + 'img_id_str': '46383430', + 'title': '', + 'user_id': 1401628, + 'width': 5168 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 431997369, + 'img_id_str': '431997369', + 'title': '', + 'user_id': 1401628, + 'width': 4112 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 597672354, + 'img_id_str': '597672354', + 'title': '', + 'user_id': 1401628, + 'width': 5168 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 285328103, + 'img_id_str': '285328103', + 'title': '', + 'user_id': 1401628, + 'width': 5168 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3448, + 'img_id': 107790432, + 'img_id_str': '107790432', + 'title': '', + 'user_id': 1401628, + 'width': 5168 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '42', + 'passed_time': '2019年12月05日', + 'post_id': 59947400, + 'published_at': '2019-12-05 23:24:27', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 21, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 17304, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1401628_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '青子wj', + 'site_id': '1401628', + 'type': 'user', + 'url': 'https://tuchong.com/1401628/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1401628', + 'sites': [], + 'tags': ['人像'], + 'title': '不问归期', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1401628/59947400/', + 'views': 58185 + }, + { + 'author_id': '1468214', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 25, + 'content': '《夏末的和声》', + 'created_at': '2019-10-14 22:46:39', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '《夏末的和声》', + 'favorite_list_prefix': [], + 'favorites': 751, + 'image_count': 24, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 608416221, + 'img_id_str': '608416221', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 261272384, + 'img_id_str': '261272384', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 573420494, + 'img_id_str': '573420494', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 166048421, + 'img_id_str': '166048421', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6828, + 'img_id': 362066395, + 'img_id_str': '362066395', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6432, + 'img_id': 212251404, + 'img_id_str': '212251404', + 'title': '001', + 'user_id': 1468214, + 'width': 4384 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 399094070, + 'img_id_str': '399094070', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 614118156, + 'img_id_str': '614118156', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 605991892, + 'img_id_str': '605991892', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 254652742, + 'img_id_str': '254652742', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 642625980, + 'img_id_str': '642625980', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 49853025, + 'img_id_str': '49853025', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 198750560, + 'img_id_str': '198750560', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 334213934, + 'img_id_str': '334213934', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 610644674, + 'img_id_str': '610644674', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 329822691, + 'img_id_str': '329822691', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 634500564, + 'img_id_str': '634500564', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 146977626, + 'img_id_str': '146977626', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 119780335, + 'img_id_str': '119780335', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6504, + 'img_id': 271692897, + 'img_id_str': '271692897', + 'title': '001', + 'user_id': 1468214, + 'width': 4336 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 448443121, + 'img_id_str': '448443121', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 564245350, + 'img_id_str': '564245350', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6392, + 'img_id': 338408454, + 'img_id_str': '338408454', + 'title': '001', + 'user_id': 1468214, + 'width': 4261 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 546550290, + 'img_id_str': '546550290', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '23', + 'passed_time': '2019年10月14日', + 'post_id': 55920277, + 'published_at': '2019-10-14 22:46:39', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 24, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 71180, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1468214_7', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'GK__', + 'site_id': '1468214', + 'type': 'user', + 'url': 'https://tuchong.com/1468214/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1468214', + 'sites': [], + 'tags': ['人像'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1468214/55920277/', + 'views': 29610 + }, + { + 'author_id': '1737793', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 18, + 'content': '天空', + 'created_at': '2019-07-30 21:24:48', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['我要上开屏'], + 'excerpt': '天空', + 'favorite_list_prefix': [], + 'favorites': 718, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4464, + 'img_id': 608469570, + 'img_id_str': '608469570', + 'title': '1200830', + 'user_id': 1737793, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 601260572, + 'img_id_str': '601260572', + 'title': '1200829', + 'user_id': 1737793, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 122585222, + 'img_id_str': '122585222', + 'title': '1200791', + 'user_id': 1737793, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3648, + 'img_id': 193626308, + 'img_id_str': '193626308', + 'title': '1200828', + 'user_id': 1737793, + 'width': 5472 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3648, + 'img_id': 402686436, + 'img_id_str': '402686436', + 'title': '1200833', + 'user_id': 1737793, + 'width': 5472 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 163545558, + 'img_id_str': '163545558', + 'title': '1200834', + 'user_id': 1737793, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3648, + 'img_id': 618693047, + 'img_id_str': '618693047', + 'title': '1200831', + 'user_id': 1737793, + 'width': 5472 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 568951105, + 'img_id_str': '568951105', + 'title': '1200832', + 'user_id': 1737793, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 235963065, + 'img_id_str': '235963065', + 'title': '1200826', + 'user_id': 1737793, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3648, + 'img_id': 616006157, + 'img_id_str': '616006157', + 'title': '1200790', + 'user_id': 1737793, + 'width': 5472 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3648, + 'img_id': 583172277, + 'img_id_str': '583172277', + 'title': '1200789', + 'user_id': 1737793, + 'width': 5472 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '15', + 'passed_time': '2019年07月30日', + 'post_id': 46963068, + 'published_at': '2019-07-30 21:24:48', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 18, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 3037, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1737793_14', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '米线95', + 'site_id': '1737793', + 'type': 'user', + 'url': 'https://tuchong.com/1737793/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1737793', + 'sites': [], + 'tags': ['我要上开屏', '小清新', '日系', '美女', '50mm', '情绪'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1737793/46963068/', + 'views': 24285 + }, + { + 'author_id': '3094448', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 54, + 'content': '红色点裙子遇上美丽的光线!', + 'created_at': '2019-09-04 15:24:26', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '红色点裙子遇上美丽的光线!', + 'favorite_list_prefix': [], + 'favorites': 1147, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3091, + 'img_id': 359701902, + 'img_id_str': '359701902', + 'title': '230016', + 'user_id': 3094448, + 'width': 2060 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3091, + 'img_id': 532913707, + 'img_id_str': '532913707', + 'title': '230010', + 'user_id': 3094448, + 'width': 2060 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3091, + 'img_id': 429497738, + 'img_id_str': '429497738', + 'title': '230014', + 'user_id': 3094448, + 'width': 2060 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2060, + 'img_id': 282042282, + 'img_id_str': '282042282', + 'title': '230011', + 'user_id': 3094448, + 'width': 3091 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3091, + 'img_id': 620273391, + 'img_id_str': '620273391', + 'title': '230013', + 'user_id': 3094448, + 'width': 2060 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3091, + 'img_id': 238460799, + 'img_id_str': '238460799', + 'title': '230009', + 'user_id': 3094448, + 'width': 2060 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3091, + 'img_id': 291937852, + 'img_id_str': '291937852', + 'title': '230012', + 'user_id': 3094448, + 'width': 2060 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3091, + 'img_id': 629841114, + 'img_id_str': '629841114', + 'title': '230015', + 'user_id': 3094448, + 'width': 2060 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3091, + 'img_id': 592027583, + 'img_id_str': '592027583', + 'title': '230017', + 'user_id': 3094448, + 'width': 2060 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '53', + 'passed_time': '2019年09月04日', + 'post_id': 51625094, + 'published_at': '2019-09-04 15:24:26', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 68, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 4527, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3094448_4', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '大猫与茶树', + 'site_id': '3094448', + 'type': 'user', + 'url': 'https://tuchong.com/3094448/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3094448', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3094448/51625094/', + 'views': 32566 + }, + { + 'author_id': '15956661', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '天津', + 'created_at': '2020-01-19 15:37:07', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['图虫城市建筑'], + 'excerpt': '天津', + 'favorite_list_prefix': [], + 'favorites': 11, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3024, + 'img_id': 638961453, + 'img_id_str': '638961453', + 'title': '001', + 'user_id': 15956661, + 'width': 4032 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2763, + 'img_id': 35178604, + 'img_id_str': '35178604', + 'title': '001', + 'user_id': 15956661, + 'width': 4032 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月19日', + 'post_id': 61839634, + 'published_at': '2020-01-19 15:37:07', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '师承EDC 给光就能拍 喜欢加关注互相学习 v:superheroZD', + 'domain': '', + 'followers': 23, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15956661_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '余生很苦你很甜', + 'site_id': '15956661', + 'type': 'user', + 'url': 'https://tuchong.com/15956661/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15956661', + 'sites': [], + 'tags': ['图虫城市建筑', '赛博朋克滤镜', '天际线', '天津'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15956661/61839634/', + 'views': 710 + }, + { + 'author_id': '8603934', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '', + 'created_at': '2020-01-19 10:22:37', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['华为摄影爱好者', '漳州市手机摄影协会', '带我看看,你的城市'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 29, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 487769821, + 'img_id_str': '487769821', + 'title': '734821', + 'user_id': 8603934, + 'width': 3456 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '01月19日', + 'post_id': 61833062, + 'published_at': '2020-01-19 10:22:37', + 'recommend': true, + 'recom_type': '', + 'rewardable': false, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '只用华为手机拍摄', + 'domain': '', + 'followers': 309, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_8603934_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'dlzxy远山', + 'site_id': '8603934', + 'type': 'user', + 'url': 'https://tuchong.com/8603934/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '8603934', + 'sites': [], + 'tags': ['华为摄影爱好者', '漳州市手机摄影协会', '带我看看,你的城市'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/8603934/61833062/', + 'views': 1084 + }, + { + 'author_id': '15947431', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '深圳·晴雨难测', + 'created_at': '2020-01-19 09:37:26', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '晒晒旅行打卡照', + '一起去拍城市天际线', + '带我看看,你的城市', + '街拍俱乐部圈子', + '街事圈子', + '街头拍客', + '分享决定性瞬间' + ], + 'excerpt': '深圳·晴雨难测', + 'favorite_list_prefix': [], + 'favorites': 13, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2439, + 'img_id': 636012692, + 'img_id_str': '636012692', + 'title': '001', + 'user_id': 15947431, + 'width': 3264 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2448, + 'img_id': 126142216, + 'img_id_str': '126142216', + 'title': '001', + 'user_id': 15947431, + 'width': 3264 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月19日', + 'post_id': 61832090, + 'published_at': '2020-01-19 09:37:26', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '一期一会', + 'domain': '', + 'followers': 12, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15947431_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '巴茨先生', + 'site_id': '15947431', + 'type': 'user', + 'url': 'https://tuchong.com/15947431/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15947431', + 'sites': [], + 'tags': ['晒晒旅行打卡照', '一起去拍城市天际线', '带我看看,你的城市', '街拍俱乐部圈子', '街事圈子', '街头拍客'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15947431/61832090/', + 'views': 601 + }, + { + 'author_id': '8089423', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': '红尘艳(二)\n娴静妖娆点头羞,一入红尘己夏秋;\n借问何时悟禅意,佛坐莲台捋佛珠。', + 'created_at': '2020-01-18 22:29:14', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['火烛一花精品摄影'], + 'excerpt': '红尘艳(二)\n娴静妖娆点头羞,一入红尘己夏秋;\n借问何时悟禅意,佛坐莲台捋佛珠。', + 'favorite_list_prefix': [], + 'favorites': 15, + 'image_count': 19, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3216, + 'img_id': 443074078, + 'img_id_str': '443074078', + 'title': '690', + 'user_id': 8089423, + 'width': 4288 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4288, + 'img_id': 502253450, + 'img_id_str': '502253450', + 'title': '692', + 'user_id': 8089423, + 'width': 3216 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3216, + 'img_id': 137086554, + 'img_id_str': '137086554', + 'title': '687', + 'user_id': 8089423, + 'width': 4288 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4288, + 'img_id': 540788893, + 'img_id_str': '540788893', + 'title': '728', + 'user_id': 8089423, + 'width': 3216 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4288, + 'img_id': 472368861, + 'img_id_str': '472368861', + 'title': '716', + 'user_id': 8089423, + 'width': 3216 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4288, + 'img_id': 237946645, + 'img_id_str': '237946645', + 'title': '809', + 'user_id': 8089423, + 'width': 3216 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4288, + 'img_id': 630507426, + 'img_id_str': '630507426', + 'title': '767', + 'user_id': 8089423, + 'width': 3216 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4288, + 'img_id': 312264823, + 'img_id_str': '312264823', + 'title': '758', + 'user_id': 8089423, + 'width': 3216 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3216, + 'img_id': 60278214, + 'img_id_str': '60278214', + 'title': '681', + 'user_id': 8089423, + 'width': 4288 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4288, + 'img_id': 613664692, + 'img_id_str': '613664692', + 'title': '891', + 'user_id': 8089423, + 'width': 3216 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4288, + 'img_id': 159696734, + 'img_id_str': '159696734', + 'title': '917', + 'user_id': 8089423, + 'width': 3216 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4288, + 'img_id': 129681278, + 'img_id_str': '129681278', + 'title': '905', + 'user_id': 8089423, + 'width': 3216 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4288, + 'img_id': 530696031, + 'img_id_str': '530696031', + 'title': '824', + 'user_id': 8089423, + 'width': 3216 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3216, + 'img_id': 433571907, + 'img_id_str': '433571907', + 'title': '876', + 'user_id': 8089423, + 'width': 4288 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3216, + 'img_id': 49269079, + 'img_id_str': '49269079', + 'title': '873', + 'user_id': 8089423, + 'width': 4288 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3216, + 'img_id': 319670203, + 'img_id_str': '319670203', + 'title': '719', + 'user_id': 8089423, + 'width': 4288 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4288, + 'img_id': 617334313, + 'img_id_str': '617334313', + 'title': '789', + 'user_id': 8089423, + 'width': 3216 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3216, + 'img_id': 385206081, + 'img_id_str': '385206081', + 'title': '828', + 'user_id': 8089423, + 'width': 4288 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3216, + 'img_id': 639289324, + 'img_id_str': '639289324', + 'title': '4462', + 'user_id': 8089423, + 'width': 4288 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月18日', + 'post_id': 61823614, + 'published_at': '2020-01-18 22:29:14', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 3, + 'site': { + 'description': '', + 'domain': '', + 'followers': 14, + 'has_everphoto_note': true, + 'icon': 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_u_0', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '用户155719282960626', + 'site_id': '8089423', + 'type': 'user', + 'url': 'https://tuchong.com/8089423/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '8089423', + 'sites': [], + 'tags': ['火烛一花精品摄影', '花园', '绽放', '花', '植物'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/8089423/61823614/', + 'views': 610 + }, + { + 'author_id': '15950662', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '月上柳梢头~', + 'created_at': '2020-01-18 22:02:25', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '中国手机摄影', + '拿起手机人人都是摄影师', + '我用手机修照片', + '手机摄影小分队', + '华为摄影爱好者', + '取景器里的小美好', + '风光摄影圈', + '城市·夜晚', + '街头拍客', + '一个风光摄影圈' + ], + 'excerpt': '月上柳梢头~', + 'favorite_list_prefix': [], + 'favorites': 14, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 331597459, + 'img_id_str': '331597459', + 'title': '410208', + 'user_id': 15950662, + 'width': 2160 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月18日', + 'post_id': 61822764, + 'published_at': '2020-01-18 22:02:25', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '用镜头记录生活中的每一个美好瞬间~', + 'domain': '', + 'followers': 8, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15950662_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '小晓雅', + 'site_id': '15950662', + 'type': 'user', + 'url': 'https://tuchong.com/15950662/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15950662', + 'sites': [], + 'tags': [ + '中国手机摄影', + '拿起手机人人都是摄影师', + '我用手机修照片', + '手机摄影小分队', + '华为摄影爱好者', + '取景器里的小美好' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15950662/61822764/', + 'views': 610 + }, + { + 'author_id': '15954090', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '是温柔的冬樱花啊', + 'created_at': '2020-01-18 20:31:21', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['图虫旅行摄影圈子'], + 'excerpt': '是温柔的冬樱花啊', + 'favorite_list_prefix': [], + 'favorites': 12, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2717, + 'img_id': 205638074, + 'img_id_str': '205638074', + 'title': '001', + 'user_id': 15954090, + 'width': 1529 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2350, + 'img_id': 51103323, + 'img_id_str': '51103323', + 'title': '001', + 'user_id': 15954090, + 'width': 1321 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2114, + 'img_id': 540460919, + 'img_id_str': '540460919', + 'title': '001', + 'user_id': 15954090, + 'width': 1191 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3760, + 'img_id': 304203386, + 'img_id_str': '304203386', + 'title': '001', + 'user_id': 15954090, + 'width': 2114 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3704, + 'img_id': 618841523, + 'img_id_str': '618841523', + 'title': '001', + 'user_id': 15954090, + 'width': 2084 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3943, + 'img_id': 610518560, + 'img_id_str': '610518560', + 'title': '001', + 'user_id': 15954090, + 'width': 2218 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5596, + 'img_id': 392153253, + 'img_id_str': '392153253', + 'title': '001', + 'user_id': 15954090, + 'width': 3148 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 617989591, + 'img_id_str': '617989591', + 'title': '001', + 'user_id': 15954090, + 'width': 3374 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2207, + 'img_id': 34851110, + 'img_id_str': '34851110', + 'title': '001', + 'user_id': 15954090, + 'width': 1232 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月18日', + 'post_id': 61820195, + 'published_at': '2020-01-18 20:31:21', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 3, + 'site': { + 'description': '快乐存甜罐´•ᴥ•`', + 'domain': '', + 'followers': 3, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15954090_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Evelyn是只兔子', + 'site_id': '15954090', + 'type': 'user', + 'url': 'https://tuchong.com/15954090/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15954090', + 'sites': [], + 'tags': ['图虫旅行摄影圈子', '花', '樱桃木', '绽放', '季节', '明亮'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15954090/61820195/', + 'views': 541 + }, + { + 'author_id': '15948588', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '山水人间', + 'created_at': '2020-01-18 19:43:45', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['最满意的风光照'], + 'excerpt': '山水人间', + 'favorite_list_prefix': [], + 'favorites': 13, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 393201197, + 'img_id_str': '393201197', + 'title': '923529', + 'user_id': 15948588, + 'width': 963 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月18日', + 'post_id': 61818871, + 'published_at': '2020-01-18 19:43:45', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '鲜花、美酒、美人,而我只喜欢你', + 'domain': '', + 'followers': 5, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15948588_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '朝夏与酒', + 'site_id': '15948588', + 'type': 'user', + 'url': 'https://tuchong.com/15948588/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15948588', + 'sites': [], + 'tags': ['最满意的风光照', '小瀑布', '岩石'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15948588/61818871/', + 'views': 665 + }, + { + 'author_id': '15953166', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '老街•巷子\n偶遇\n手工皮鞋铺', + 'created_at': '2020-01-18 18:10:20', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['带我看看,你的城市', '人文天下'], + 'excerpt': '老街•巷子\n偶遇\n手工皮鞋铺', + 'favorite_list_prefix': [], + 'favorites': 13, + 'image_count': 6, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3163, + 'img_id': 574736049, + 'img_id_str': '574736049', + 'title': '1611759', + 'user_id': 15953166, + 'width': 5619 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 59820083, + 'img_id_str': '59820083', + 'title': '1611758', + 'user_id': 15953166, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5540, + 'img_id': 620087198, + 'img_id_str': '620087198', + 'title': '1611760', + 'user_id': 15953166, + 'width': 3121 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5814, + 'img_id': 533972433, + 'img_id_str': '533972433', + 'title': '1611757', + 'user_id': 15953166, + 'width': 3876 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 187615218, + 'img_id_str': '187615218', + 'title': '1611761', + 'user_id': 15953166, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 57591273, + 'img_id_str': '57591273', + 'title': '1607304', + 'user_id': 15953166, + 'width': 4000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月18日', + 'post_id': 61816743, + 'published_at': '2020-01-18 18:10:20', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 8, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15953166_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '阿雷要努力鸭', + 'site_id': '15953166', + 'type': 'user', + 'url': 'https://tuchong.com/15953166/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15953166', + 'sites': [], + 'tags': ['带我看看,你的城市', '人文天下', '城市', '纪实'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15953166/61816743/', + 'views': 723 + }, + { + 'author_id': '15951067', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '', + 'created_at': '2020-01-18 14:26:01', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['图虫旅行摄影圈子'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 18, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 80922504, + 'img_id_str': '80922504', + 'title': '314700', + 'user_id': 15951067, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 358336148, + 'img_id_str': '358336148', + 'title': '314703', + 'user_id': 15951067, + 'width': 1080 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '01月18日', + 'post_id': 61811892, + 'published_at': '2020-01-18 14:26:01', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 2, + 'site': { + 'description': '', + 'domain': '', + 'followers': 7, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15951067_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '勇者不惧909', + 'site_id': '15951067', + 'type': 'user', + 'url': 'https://tuchong.com/15951067/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15951067', + 'sites': [], + 'tags': ['图虫旅行摄影圈子'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15951067/61811892/', + 'views': 839 + }, + { + 'author_id': '15935548', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '机场的尽头通往回家的路\n升起的月亮将见证我们相聚', + 'created_at': '2020-01-18 09:41:24', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['生活小确幸', '街拍纪实手册'], + 'excerpt': '机场的尽头通往回家的路\n升起的月亮将见证我们相聚', + 'favorite_list_prefix': [], + 'favorites': 12, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2665, + 'img_id': 154847060, + 'img_id_str': '154847060', + 'title': '001', + 'user_id': 15935548, + 'width': 3556 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月18日', + 'post_id': 61805805, + 'published_at': '2020-01-18 09:41:24', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '仅做记录之用', + 'domain': '', + 'followers': 4, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15935548_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '蓝蓝柒', + 'site_id': '15935548', + 'type': 'user', + 'url': 'https://tuchong.com/15935548/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15935548', + 'sites': [], + 'tags': ['生活小确幸', '街拍纪实手册', '古典蓝晒', 'Toning1滤镜', '机场'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15935548/61805805/', + 'views': 601 + }, + { + 'author_id': '15947431', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '大连体育中心', + 'created_at': '2020-01-18 08:13:21', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '带我看看,你的城市', + '华为摄影爱好者', + '我们都是城市探险家', + '图虫旅行摄影圈子', + '极简主义,少即是多' + ], + 'excerpt': '大连体育中心', + 'favorite_list_prefix': [], + 'favorites': 14, + 'image_count': 8, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2736, + 'img_id': 271173705, + 'img_id_str': '271173705', + 'title': '001', + 'user_id': 15947431, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2345, + 'img_id': 580240931, + 'img_id_str': '580240931', + 'title': '001', + 'user_id': 15947431, + 'width': 3516 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2333, + 'img_id': 433178939, + 'img_id_str': '433178939', + 'title': '001', + 'user_id': 15947431, + 'width': 3070 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2736, + 'img_id': 107071148, + 'img_id_str': '107071148', + 'title': '001', + 'user_id': 15947431, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2736, + 'img_id': 462079880, + 'img_id_str': '462079880', + 'title': '001', + 'user_id': 15947431, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2736, + 'img_id': 202688013, + 'img_id_str': '202688013', + 'title': '001', + 'user_id': 15947431, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2736, + 'img_id': 223200942, + 'img_id_str': '223200942', + 'title': '001', + 'user_id': 15947431, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2736, + 'img_id': 178047037, + 'img_id_str': '178047037', + 'title': '001', + 'user_id': 15947431, + 'width': 3648 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月18日', + 'post_id': 61804174, + 'published_at': '2020-01-18 08:13:21', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '一期一会', + 'domain': '', + 'followers': 12, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15947431_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '巴茨先生', + 'site_id': '15947431', + 'type': 'user', + 'url': 'https://tuchong.com/15947431/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15947431', + 'sites': [], + 'tags': [ + '带我看看,你的城市', + '华为摄影爱好者', + '我们都是城市探险家', + '图虫旅行摄影圈子', + '极简主义,少即是多', + '体育场' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15947431/61804174/', + 'views': 427 + }, + { + 'author_id': '15938645', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': + '一千四百多年历史的双林寺,距离平遥高铁站很近,高铁站始发的108路公交车只需一站地。在晋中热门人气排行榜上它排在20名开外,在这个看颜值的时代,它的确并不讨喜。双林寺外表真的很朴实,墙不高大,殿堂矮小,就连这一千岁的国槐长的都那么低调。但它在佛学界和艺术界却享有着非常高的声誉。', + 'created_at': '2020-01-18 07:18:40', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['各地教堂大赏'], + 'excerpt': + '一千四百多年历史的双林寺,距离平遥高铁站很近,高铁站始发的108路公交车只需一站地。在晋中热门人气排行榜上它排在20名开外,在这个看颜值的时代,它的确并不讨喜。双林寺外表真的很朴实,墙不高大,殿堂矮小,就连这一千岁的国槐长的都那么低调。但它在佛学界和艺术界却享有着非常高的声誉。', + 'favorite_list_prefix': [], + 'favorites': 11, + 'image_count': 5, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 528533615, + 'img_id_str': '528533615', + 'title': '740518', + 'user_id': 15938645, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 568510052, + 'img_id_str': '568510052', + 'title': '740517', + 'user_id': 15938645, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 206424010, + 'img_id_str': '206424010', + 'title': '740516', + 'user_id': 15938645, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 382584340, + 'img_id_str': '382584340', + 'title': '740515', + 'user_id': 15938645, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4608, + 'img_id': 540068198, + 'img_id_str': '540068198', + 'title': '740514', + 'user_id': 15938645, + 'width': 3456 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月18日', + 'post_id': 61803501, + 'published_at': '2020-01-18 07:18:40', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': + '无论走到哪里\n都应该记住\n过去都是假的\n回忆是一条没有尽头的路\n一切以往的春天都不复存在\n就连那最坚韧而又狂乱的爱情\n归根结底也不过是一种转瞬即逝的现实\n唯有孤独永恒', + 'domain': '', + 'followers': 16, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15938645_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'waterfallice', + 'site_id': '15938645', + 'type': 'user', + 'url': 'https://tuchong.com/15938645/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15938645', + 'sites': [], + 'tags': ['各地教堂大赏'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15938645/61803501/', + 'views': 435 + }, + { + 'author_id': '4463835', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '家乡的村庄', + 'created_at': '2020-01-16 17:43:55', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['风光摄影集'], + 'excerpt': '家乡的村庄', + 'favorite_list_prefix': [], + 'favorites': 17, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3295, + 'img_id': 262195258, + 'img_id_str': '262195258', + 'title': '208150', + 'user_id': 4463835, + 'width': 5184 + }, + { + 'description': '', + 'excerpt': '', + 'height': 999, + 'img_id': 335464520, + 'img_id_str': '335464520', + 'title': '207834', + 'user_id': 4463835, + 'width': 2160 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月16日', + 'post_id': 61761626, + 'published_at': '2020-01-16 17:43:55', + 'recommend': true, + 'recom_type': '', + 'rewardable': false, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '摄我所爱无关名利', + 'domain': '', + 'followers': 386, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_4463835_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '李非洋', + 'site_id': '4463835', + 'type': 'user', + 'url': 'https://tuchong.com/4463835/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '4463835', + 'sites': [], + 'tags': ['风光摄影集', '天空', '建筑物', '住房', '房子'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/4463835/61761626/', + 'views': 880 + }, + { + 'author_id': '458707', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 47, + 'content': '', + 'created_at': '2019-11-04 17:41:41', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 2211, + 'image_count': 21, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1334, + 'img_id': 614971914, + 'img_id_str': '614971914', + 'title': '', + 'user_id': 458707, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 604486319, + 'img_id_str': '604486319', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 311737134, + 'img_id_str': '311737134', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 402504266, + 'img_id_str': '402504266', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 301251202, + 'img_id_str': '301251202', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 166836684, + 'img_id_str': '166836684', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 123124381, + 'img_id_str': '123124381', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1334, + 'img_id': 506510374, + 'img_id_str': '506510374', + 'title': '', + 'user_id': 458707, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 128825800, + 'img_id_str': '128825800', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 148552312, + 'img_id_str': '148552312', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 655080862, + 'img_id_str': '655080862', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 200326299, + 'img_id_str': '200326299', + 'title': '', + 'user_id': 458707, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 179222989, + 'img_id_str': '179222989', + 'title': '', + 'user_id': 458707, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 338475449, + 'img_id_str': '338475449', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 202750585, + 'img_id_str': '202750585', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 477739683, + 'img_id_str': '477739683', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 447265062, + 'img_id_str': '447265062', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 523876778, + 'img_id_str': '523876778', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 654883737, + 'img_id_str': '654883737', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 520796736, + 'img_id_str': '520796736', + 'title': '', + 'user_id': 458707, + 'width': 1334 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 566147436, + 'img_id_str': '566147436', + 'title': '', + 'user_id': 458707, + 'width': 1334 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '47', + 'passed_time': '2019年11月04日', + 'post_id': 57789518, + 'published_at': '2019-11-04 17:41:41', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '2', + 'rqt_id': '', + 'shares': 68, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 8735, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_458707_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '青焱', + 'site_id': '458707', + 'type': 'user', + 'url': 'https://tuchong.com/458707/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '458707', + 'sites': [], + 'tags': ['人像', '小清新', '日系', '长沙'], + 'title': '爱笑的眼睛', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/458707/57789518/', + 'views': 96677 + }, + { + 'author_id': '1468214', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 42, + 'content': '《夏有三木》', + 'created_at': '2019-10-09 17:47:49', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '《夏有三木》', + 'favorite_list_prefix': [], + 'favorites': 1314, + 'image_count': 16, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 617262885, + 'img_id_str': '617262885', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 413380778, + 'img_id_str': '413380778', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 124826083, + 'img_id_str': '124826083', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 408334788, + 'img_id_str': '408334788', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 420327821, + 'img_id_str': '420327821', + 'title': '001', + 'user_id': 1468214, + 'width': 6528 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 128823316, + 'img_id_str': '128823316', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4600, + 'img_id': 453095940, + 'img_id_str': '453095940', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4584, + 'img_id': 176009200, + 'img_id_str': '176009200', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 416198466, + 'img_id_str': '416198466', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 281981005, + 'img_id_str': '281981005', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 163229643, + 'img_id_str': '163229643', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 109228259, + 'img_id_str': '109228259', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 575517059, + 'img_id_str': '575517059', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 359575674, + 'img_id_str': '359575674', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 98545687, + 'img_id_str': '98545687', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 573879010, + 'img_id_str': '573879010', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '39', + 'passed_time': '2019年10月09日', + 'post_id': 55373134, + 'published_at': '2019-10-09 17:47:49', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 22, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 71180, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1468214_7', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'GK__', + 'site_id': '1468214', + 'type': 'user', + 'url': 'https://tuchong.com/1468214/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1468214', + 'sites': [], + 'tags': ['人像'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1468214/55373134/', + 'views': 66215 + }, + { + 'author_id': '1066625', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 29, + 'content': '摄影师:SOOHU\n灯光:木白\n化妆/造型师:李文静\n模特:张萌\n后期:银鹏工作室', + 'created_at': '2019-10-03 18:36:26', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '摄影师:SOOHU\n灯光:木白\n化妆/造型师:李文静\n模特:张萌\n后期:银鹏工作室', + 'favorite_list_prefix': [], + 'favorites': 440, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2705, + 'img_id': 337357746, + 'img_id_str': '337357746', + 'title': '', + 'user_id': 1066625, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2705, + 'img_id': 336702505, + 'img_id_str': '336702505', + 'title': '', + 'user_id': 1066625, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2705, + 'img_id': 542551319, + 'img_id_str': '542551319', + 'title': '', + 'user_id': 1066625, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1358, + 'img_id': 562933171, + 'img_id_str': '562933171', + 'title': '', + 'user_id': 1066625, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 831, + 'img_id': 633776989, + 'img_id_str': '633776989', + 'title': '', + 'user_id': 1066625, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2705, + 'img_id': 399486016, + 'img_id_str': '399486016', + 'title': '', + 'user_id': 1066625, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1359, + 'img_id': 639609739, + 'img_id_str': '639609739', + 'title': '', + 'user_id': 1066625, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1198, + 'img_id': 415215159, + 'img_id_str': '415215159', + 'title': '', + 'user_id': 1066625, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1198, + 'img_id': 624929701, + 'img_id_str': '624929701', + 'title': '', + 'user_id': 1066625, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2705, + 'img_id': 347188228, + 'img_id_str': '347188228', + 'title': '', + 'user_id': 1066625, + 'width': 1800 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '28', + 'passed_time': '2019年10月03日', + 'post_id': 54438188, + 'published_at': '2019-10-03 18:36:26', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 41, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 7538, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1066625_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '银鹏', + 'site_id': '1066625', + 'type': 'user', + 'url': 'https://tuchong.com/1066625/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1066625', + 'sites': [], + 'tags': ['人像', '美女'], + 'title': '蜜桃臀', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1066625/54438188/', + 'views': 33568 + }, + { + 'author_id': '2349130', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 21, + 'content': '奶油般温柔', + 'created_at': '2019-09-26 10:33:06', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['分享神仙颜值'], + 'excerpt': '奶油般温柔', + 'favorite_list_prefix': [], + 'favorites': 585, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 583182110, + 'img_id_str': '583182110', + 'title': '001', + 'user_id': 2349130, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 363899098, + 'img_id_str': '363899098', + 'title': '001', + 'user_id': 2349130, + 'width': 3840 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '17', + 'passed_time': '2019年09月26日', + 'post_id': 53600326, + 'published_at': '2019-09-26 10:33:06', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 11, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 3391, + 'has_everphoto_note': false, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2349130_7', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '羊小暖', + 'site_id': '2349130', + 'type': 'user', + 'url': 'https://tuchong.com/2349130/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2349130', + 'sites': [], + 'tags': ['分享神仙颜值', '人像', '美女', '佳能', '日系', '少女写真'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2349130/53600326/', + 'views': 16727 + }, + { + 'author_id': '2326533', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 44, + 'content': '舞者来自中央芭蕾舞团王佳宝,这是在中国铁道博物馆拍摄的,想表现古老与现代的相遇,柔与刚的碰撞,', + 'created_at': '2019-09-18 18:21:07', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '舞者来自中央芭蕾舞团王佳宝,这是在中国铁道博物馆拍摄的,想表现古老与现代的相遇,柔与刚的碰撞,', + 'favorite_list_prefix': [], + 'favorites': 847, + 'image_count': 6, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2689, + 'img_id': 393258177, + 'img_id_str': '393258177', + 'title': '', + 'user_id': 2326533, + 'width': 4032 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2689, + 'img_id': 529376215, + 'img_id_str': '529376215', + 'title': '', + 'user_id': 2326533, + 'width': 4032 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2689, + 'img_id': 504603328, + 'img_id_str': '504603328', + 'title': '', + 'user_id': 2326533, + 'width': 4032 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2689, + 'img_id': 188589512, + 'img_id_str': '188589512', + 'title': '', + 'user_id': 2326533, + 'width': 4032 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 354984830, + 'img_id_str': '354984830', + 'title': '', + 'user_id': 2326533, + 'width': 4032 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2689, + 'img_id': 451257600, + 'img_id_str': '451257600', + 'title': '', + 'user_id': 2326533, + 'width': 4032 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '43', + 'passed_time': '2019年09月18日', + 'post_id': 52931816, + 'published_at': '2019-09-18 18:21:07', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 24, + 'site': { + 'description': '光影中立定精神 . 镜头下决出生活', + 'domain': 'shilei.tuchong.com', + 'followers': 2892, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2326533_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '石磊-空境舞集摄影', + 'site_id': '2326533', + 'type': 'user', + 'url': 'https://shilei.tuchong.com/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '2326533', + 'sites': [], + 'tags': ['舞蹈', '舞蹈摄影', '芭蕾舞', '现代芭蕾', '线条', '火车'], + 'title': '当芭蕾遇上火车', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://shilei.tuchong.com/52931816/', + 'views': 49116 + }, + { + 'author_id': '3477512', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 97, + 'content': '上海夜景-魔都三件套', + 'created_at': '2019-08-23 21:48:11', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '上海夜景-魔都三件套', + 'favorite_list_prefix': [], + 'favorites': 2607, + 'image_count': 13, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 394761957, + 'img_id_str': '394761957', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 347379130, + 'img_id_str': '347379130', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 491689982, + 'img_id_str': '491689982', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 236754869, + 'img_id_str': '236754869', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 74749519, + 'img_id_str': '74749519', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 391812813, + 'img_id_str': '391812813', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 456628112, + 'img_id_str': '456628112', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 233019363, + 'img_id_str': '233019363', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 636458661, + 'img_id_str': '636458661', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 537630454, + 'img_id_str': '537630454', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 279549604, + 'img_id_str': '279549604', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 101750610, + 'img_id_str': '101750610', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 158897918, + 'img_id_str': '158897918', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '80', + 'passed_time': '2019年08月23日', + 'post_id': 50371085, + 'published_at': '2019-08-23 21:48:11', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 80, + 'site': { + 'description': '资深旅行摄影师', + 'domain': '', + 'followers': 44864, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3477512_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Cissy_Li', + 'site_id': '3477512', + 'type': 'user', + 'url': 'https://tuchong.com/3477512/', + 'verification_list': [ + {'verification_reason': '资深旅行摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3477512', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3477512/50371085/', + 'views': 112505 + }, + { + 'author_id': '2574940', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 75, + 'content': '', + 'created_at': '2019-08-22 15:35:23', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 2485, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 599299386, + 'img_id_str': '599299386', + 'title': '', + 'user_id': 2574940, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 533436405, + 'img_id_str': '533436405', + 'title': '', + 'user_id': 2574940, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 509581284, + 'img_id_str': '509581284', + 'title': '', + 'user_id': 2574940, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 329553488, + 'img_id_str': '329553488', + 'title': '', + 'user_id': 2574940, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 309958236, + 'img_id_str': '309958236', + 'title': '', + 'user_id': 2574940, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 82941327, + 'img_id_str': '82941327', + 'title': '', + 'user_id': 2574940, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 388994736, + 'img_id_str': '388994736', + 'title': '', + 'user_id': 2574940, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 438473981, + 'img_id_str': '438473981', + 'title': '', + 'user_id': 2574940, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 342201717, + 'img_id_str': '342201717', + 'title': '', + 'user_id': 2574940, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 573150702, + 'img_id_str': '573150702', + 'title': '', + 'user_id': 2574940, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 138712326, + 'img_id_str': '138712326', + 'title': '', + 'user_id': 2574940, + 'width': 3456 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '71', + 'passed_time': '2019年08月22日', + 'post_id': 50206881, + 'published_at': '2019-08-22 15:35:23', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '91', + 'rqt_id': '', + 'shares': 114, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'mixmico.tuchong.com', + 'followers': 48506, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2574940_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'MixMico米扣', + 'site_id': '2574940', + 'type': 'user', + 'url': 'https://mixmico.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2574940', + 'sites': [], + 'tags': ['人像', '色彩', '美女', '写真', '眼镜', '产品'], + 'title': 'Glasses', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://mixmico.tuchong.com/50206881/', + 'views': 97862 + }, + { + 'author_id': '985660', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 37, + 'content': '终于圆了去这里拍照的怨念··哈哈哈··开心\n出镜:巧音\n同行:@小超童鞋', + 'created_at': '2019-08-16 16:51:27', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['我们都爱日系摄影'], + 'excerpt': '终于圆了去这里拍照的怨念··哈哈哈··开心\n出镜:巧音\n同行:@小超童鞋', + 'favorite_list_prefix': [], + 'favorites': 970, + 'image_count': 14, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1088, + 'img_id': 490115637, + 'img_id_str': '490115637', + 'title': '', + 'user_id': 985660, + 'width': 1633 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1236, + 'img_id': 651858122, + 'img_id_str': '651858122', + 'title': '', + 'user_id': 985660, + 'width': 1644 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1646, + 'img_id': 570986713, + 'img_id_str': '570986713', + 'title': '', + 'user_id': 985660, + 'width': 1237 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1646, + 'img_id': 118722905, + 'img_id_str': '118722905', + 'title': '', + 'user_id': 985660, + 'width': 1237 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1088, + 'img_id': 404591297, + 'img_id_str': '404591297', + 'title': '', + 'user_id': 985660, + 'width': 1633 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1088, + 'img_id': 524062977, + 'img_id_str': '524062977', + 'title': '', + 'user_id': 985660, + 'width': 1633 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1646, + 'img_id': 204247528, + 'img_id_str': '204247528', + 'title': '', + 'user_id': 985660, + 'width': 1237 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1236, + 'img_id': 500142483, + 'img_id_str': '500142483', + 'title': '', + 'user_id': 985660, + 'width': 1644 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1646, + 'img_id': 38834538, + 'img_id_str': '38834538', + 'title': '', + 'user_id': 985660, + 'width': 1237 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1088, + 'img_id': 306483565, + 'img_id_str': '306483565', + 'title': '', + 'user_id': 985660, + 'width': 1633 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1646, + 'img_id': 217223598, + 'img_id_str': '217223598', + 'title': '', + 'user_id': 985660, + 'width': 1237 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1236, + 'img_id': 187404240, + 'img_id_str': '187404240', + 'title': '', + 'user_id': 985660, + 'width': 1644 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1088, + 'img_id': 405573504, + 'img_id_str': '405573504', + 'title': '', + 'user_id': 985660, + 'width': 1633 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1646, + 'img_id': 441684191, + 'img_id_str': '441684191', + 'title': '', + 'user_id': 985660, + 'width': 1237 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '30', + 'passed_time': '2019年08月16日', + 'post_id': 49428555, + 'published_at': '2019-08-16 16:51:27', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '7', + 'rqt_id': '', + 'shares': 28, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'patlabor.tuchong.com', + 'followers': 2609, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_985660_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '逍遥小兵', + 'site_id': '985660', + 'type': 'user', + 'url': 'https://patlabor.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '985660', + 'sites': [], + 'tags': ['我们都爱日系摄影', '人像', '小清新', '少女', '日系'], + 'title': '暑中见舞い', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://patlabor.tuchong.com/49428555/', + 'views': 58643 + }, + { + 'author_id': '3738183', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 59, + 'content': '给我一个点赞,我就把吃不胖的秘密告诉你,一般人我不告诉Ta', + 'created_at': '2019-08-15 22:35:23', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['我要上“首页推荐位”'], + 'excerpt': '给我一个点赞,我就把吃不胖的秘密告诉你,一般人我不告诉Ta', + 'favorite_list_prefix': [], + 'favorites': 1670, + 'image_count': 7, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5250, + 'img_id': 531993021, + 'img_id_str': '531993021', + 'title': '001', + 'user_id': 3738183, + 'width': 3500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5250, + 'img_id': 540316148, + 'img_id_str': '540316148', + 'title': '001', + 'user_id': 3738183, + 'width': 3500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2333, + 'img_id': 84774537, + 'img_id_str': '84774537', + 'title': '001', + 'user_id': 3738183, + 'width': 3500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5250, + 'img_id': 213618752, + 'img_id_str': '213618752', + 'title': '001', + 'user_id': 3738183, + 'width': 3500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5250, + 'img_id': 335778057, + 'img_id_str': '335778057', + 'title': '001', + 'user_id': 3738183, + 'width': 3500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5250, + 'img_id': 271617773, + 'img_id_str': '271617773', + 'title': '001', + 'user_id': 3738183, + 'width': 3500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1278, + 'img_id': 613978400, + 'img_id_str': '613978400', + 'title': '001', + 'user_id': 3738183, + 'width': 2000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '51', + 'passed_time': '2019年08月15日', + 'post_id': 49342525, + 'published_at': '2019-08-15 22:35:23', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 43, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 15518, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3738183_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '钟月月', + 'site_id': '3738183', + 'type': 'user', + 'url': 'https://tuchong.com/3738183/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3738183', + 'sites': [], + 'tags': ['我要上“首页推荐位”', '享受', '放松', '休闲'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3738183/49342525/', + 'views': 51413 + }, + { + 'author_id': '1328495', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 220, + 'content': '额济纳金秋', + 'created_at': '2019-08-09 17:44:27', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['世界华人风光摄影小组圈子'], + 'excerpt': '额济纳金秋', + 'favorite_list_prefix': [], + 'favorites': 2963, + 'image_count': 8, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2500, + 'img_id': 357272201, + 'img_id_str': '357272201', + 'title': '1566883', + 'user_id': 1328495, + 'width': 2500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1438, + 'img_id': 604933208, + 'img_id_str': '604933208', + 'title': '1566884', + 'user_id': 1328495, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 577997552, + 'img_id_str': '577997552', + 'title': '1566881', + 'user_id': 1328495, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1621, + 'img_id': 632130551, + 'img_id_str': '632130551', + 'title': '1566880', + 'user_id': 1328495, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 591628503, + 'img_id_str': '591628503', + 'title': '1566879', + 'user_id': 1328495, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 420580002, + 'img_id_str': '420580002', + 'title': '1566885', + 'user_id': 1328495, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 621185999, + 'img_id_str': '621185999', + 'title': '1566882', + 'user_id': 1328495, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 64719789, + 'img_id_str': '64719789', + 'title': '1566888', + 'user_id': 1328495, + 'width': 3000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '213', + 'passed_time': '2019年08月09日', + 'post_id': 48408355, + 'published_at': '2019-08-09 17:44:27', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 230, + 'site': { + 'description': '资深旅行摄影师', + 'domain': 'zylm8899.tuchong.com', + 'followers': 4272, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1328495_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '独客', + 'site_id': '1328495', + 'type': 'user', + 'url': 'https://zylm8899.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深旅行摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1328495', + 'sites': [], + 'tags': ['世界华人风光摄影小组圈子', '秋季', '内蒙古额济纳旗', '额济纳', '坝上主题摄影小组圈子', '水胡杨'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://zylm8899.tuchong.com/48408355/', + 'views': 90747 + }, + { + 'author_id': '2717528', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 40, + 'content': '', + 'created_at': '2019-07-22 19:41:58', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['Cosplay摄影集中地'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 921, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3360, + 'img_id': 269318827, + 'img_id_str': '269318827', + 'title': '001', + 'user_id': 2717528, + 'width': 2240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3360, + 'img_id': 431192505, + 'img_id_str': '431192505', + 'title': '001', + 'user_id': 2717528, + 'width': 2240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3360, + 'img_id': 528644393, + 'img_id_str': '528644393', + 'title': '001', + 'user_id': 2717528, + 'width': 2240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3360, + 'img_id': 328300981, + 'img_id_str': '328300981', + 'title': '001', + 'user_id': 2717528, + 'width': 2240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3360, + 'img_id': 97352169, + 'img_id_str': '97352169', + 'title': '001', + 'user_id': 2717528, + 'width': 2240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3360, + 'img_id': 45643992, + 'img_id_str': '45643992', + 'title': '001', + 'user_id': 2717528, + 'width': 2240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2240, + 'img_id': 379288230, + 'img_id_str': '379288230', + 'title': '001', + 'user_id': 2717528, + 'width': 3360 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3279, + 'img_id': 216758328, + 'img_id_str': '216758328', + 'title': '001', + 'user_id': 2717528, + 'width': 2186 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2240, + 'img_id': 622492140, + 'img_id_str': '622492140', + 'title': '001', + 'user_id': 2717528, + 'width': 3360 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2240, + 'img_id': 198146317, + 'img_id_str': '198146317', + 'title': '001', + 'user_id': 2717528, + 'width': 3360 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '39', + 'passed_time': '2019年07月22日', + 'post_id': 45726171, + 'published_at': '2019-07-22 19:41:58', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 18, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 11801, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2717528_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '卡巴拉岛的回忆', + 'site_id': '2717528', + 'type': 'user', + 'url': 'https://tuchong.com/2717528/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2717528', + 'sites': [], + 'tags': ['Cosplay摄影集中地', 'Cosplay', '人像'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2717528/45726171/', + 'views': 43788 + }, + { + 'author_id': '2598104', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 22, + 'content': '《古墓丽影》劳拉克劳馥 COS ɪɴs.enjinight ​​​', + 'created_at': '2020-01-06 14:23:52', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '《古墓丽影》劳拉克劳馥 COS ɪɴs.enjinight ​​​', + 'favorite_list_prefix': [], + 'favorites': 196, + 'image_count': 6, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1200, + 'img_id': 168413208, + 'img_id_str': '168413208', + 'title': '001', + 'user_id': 2598104, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1200, + 'img_id': 262457191, + 'img_id_str': '262457191', + 'title': '001', + 'user_id': 2598104, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1200, + 'img_id': 396870731, + 'img_id_str': '396870731', + 'title': '001', + 'user_id': 2598104, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1200, + 'img_id': 78955937, + 'img_id_str': '78955937', + 'title': '001', + 'user_id': 2598104, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1200, + 'img_id': 324322786, + 'img_id_str': '324322786', + 'title': '001', + 'user_id': 2598104, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1200, + 'img_id': 495568404, + 'img_id_str': '495568404', + 'title': '001', + 'user_id': 2598104, + 'width': 800 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '22', + 'passed_time': '01月06日', + 'post_id': 61452547, + 'published_at': '2020-01-06 14:23:52', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 8, + 'site': { + 'description': '知名摄影博主', + 'domain': '', + 'followers': 10365, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2598104_12', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '写真bot', + 'site_id': '2598104', + 'type': 'user', + 'url': 'https://tuchong.com/2598104/', + 'verification_list': [ + {'verification_reason': '知名摄影博主', 'verification_type': 12} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2598104', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2598104/61452547/', + 'views': 14214 + }, + { + 'author_id': '1475907', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 37, + 'content': '', + 'created_at': '2019-12-15 13:35:26', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 665, + 'image_count': 14, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5340, + 'img_id': 453886742, + 'img_id_str': '453886742', + 'title': '', + 'user_id': 1475907, + 'width': 3560 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5340, + 'img_id': 108054209, + 'img_id_str': '108054209', + 'title': '', + 'user_id': 1475907, + 'width': 3560 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5340, + 'img_id': 194430153, + 'img_id_str': '194430153', + 'title': '', + 'user_id': 1475907, + 'width': 3560 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 259769116, + 'img_id_str': '259769116', + 'title': '', + 'user_id': 1475907, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 498975447, + 'img_id_str': '498975447', + 'title': '', + 'user_id': 1475907, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 260621348, + 'img_id_str': '260621348', + 'title': '', + 'user_id': 1475907, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 175948795, + 'img_id_str': '175948795', + 'title': '', + 'user_id': 1475907, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 464766262, + 'img_id_str': '464766262', + 'title': '', + 'user_id': 1475907, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 410305264, + 'img_id_str': '410305264', + 'title': '', + 'user_id': 1475907, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5340, + 'img_id': 471777845, + 'img_id_str': '471777845', + 'title': '', + 'user_id': 1475907, + 'width': 3560 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 350012707, + 'img_id_str': '350012707', + 'title': '', + 'user_id': 1475907, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 86688512, + 'img_id_str': '86688512', + 'title': '', + 'user_id': 1475907, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5340, + 'img_id': 464110677, + 'img_id_str': '464110677', + 'title': '', + 'user_id': 1475907, + 'width': 3560 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3648, + 'img_id': 52216800, + 'img_id_str': '52216800', + 'title': '', + 'user_id': 1475907, + 'width': 5472 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '32', + 'passed_time': '2019年12月15日', + 'post_id': 60478104, + 'published_at': '2019-12-15 13:35:26', + 'recommend': true, + 'recom_type': '', + 'rewardable': false, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 40, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'alianya.tuchong.com', + 'followers': 2387, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1475907_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '阿脸呀alianya', + 'site_id': '1475907', + 'type': 'user', + 'url': 'https://alianya.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1475907', + 'sites': [], + 'tags': ['小清新', '日系', '旅行'], + 'title': '泳衣写真', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://alianya.tuchong.com/60478104/', + 'views': 38970 + }, + { + 'author_id': '282981', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 19, + 'content': '摄影&后期:@摄影师张翼鹏', + 'created_at': '2019-11-24 21:37:01', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '摄影&后期:@摄影师张翼鹏', + 'favorite_list_prefix': [], + 'favorites': 439, + 'image_count': 14, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 280346749, + 'img_id_str': '280346749', + 'title': '', + 'user_id': 282981, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 349094014, + 'img_id_str': '349094014', + 'title': '', + 'user_id': 282981, + 'width': 1333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 270712684, + 'img_id_str': '270712684', + 'title': '', + 'user_id': 282981, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 432324280, + 'img_id_str': '432324280', + 'title': '', + 'user_id': 282981, + 'width': 1333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 103071548, + 'img_id_str': '103071548', + 'title': '', + 'user_id': 282981, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 601866374, + 'img_id_str': '601866374', + 'title': '', + 'user_id': 282981, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 230080271, + 'img_id_str': '230080271', + 'title': '', + 'user_id': 282981, + 'width': 1333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 440057688, + 'img_id_str': '440057688', + 'title': '', + 'user_id': 282981, + 'width': 1333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 538427303, + 'img_id_str': '538427303', + 'title': '', + 'user_id': 282981, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 195542739, + 'img_id_str': '195542739', + 'title': '', + 'user_id': 282981, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 199540833, + 'img_id_str': '199540833', + 'title': '', + 'user_id': 282981, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 315080512, + 'img_id_str': '315080512', + 'title': '', + 'user_id': 282981, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 643743806, + 'img_id_str': '643743806', + 'title': '', + 'user_id': 282981, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 195346976, + 'img_id_str': '195346976', + 'title': '', + 'user_id': 282981, + 'width': 2000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '18', + 'passed_time': '2019年11月24日', + 'post_id': 59266734, + 'published_at': '2019-11-24 21:37:01', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 16, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'jiafei.tuchong.com', + 'followers': 7142, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_282981_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '加菲视觉', + 'site_id': '282981', + 'type': 'user', + 'url': 'https://jiafei.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '282981', + 'sites': [], + 'tags': ['人像', '美女', '情绪', '写真', '女生'], + 'title': '【加菲视觉】—— zixin', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://jiafei.tuchong.com/59266734/', + 'views': 17515 + }, + { + 'author_id': '959534', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 113, + 'content': '我的人像摄影教学已经开始招生,咨询报名请加vx:18310080899或majingbossma', + 'created_at': '2019-10-25 10:46:13', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '我的人像摄影教学已经开始招生,咨询报名请加vx:18310080899或majingbossma', + 'favorite_list_prefix': [], + 'favorites': 3344, + 'image_count': 14, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 523089616, + 'img_id_str': '523089616', + 'title': '', + 'user_id': 959534, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 304723546, + 'img_id_str': '304723546', + 'title': '', + 'user_id': 959534, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 313178029, + 'img_id_str': '313178029', + 'title': '', + 'user_id': 959534, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2273, + 'img_id': 295548477, + 'img_id_str': '295548477', + 'title': '', + 'user_id': 959534, + 'width': 3410 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5656, + 'img_id': 404600808, + 'img_id_str': '404600808', + 'title': '', + 'user_id': 959534, + 'width': 3771 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 557299614, + 'img_id_str': '557299614', + 'title': '', + 'user_id': 959534, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3475, + 'img_id': 49919589, + 'img_id_str': '49919589', + 'title': '', + 'user_id': 959534, + 'width': 5213 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 239581020, + 'img_id_str': '239581020', + 'title': '', + 'user_id': 959534, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3446, + 'img_id': 484226895, + 'img_id_str': '484226895', + 'title': '', + 'user_id': 959534, + 'width': 5169 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 358856009, + 'img_id_str': '358856009', + 'title': '', + 'user_id': 959534, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 632272613, + 'img_id_str': '632272613', + 'title': '', + 'user_id': 959534, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 168605607, + 'img_id_str': '168605607', + 'title': '', + 'user_id': 959534, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2952, + 'img_id': 234862244, + 'img_id_str': '234862244', + 'title': '', + 'user_id': 959534, + 'width': 4429 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 123844189, + 'img_id_str': '123844189', + 'title': '', + 'user_id': 959534, + 'width': 3840 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '98', + 'passed_time': '2019年10月25日', + 'post_id': 56875719, + 'published_at': '2019-10-25 10:46:13', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '2', + 'rqt_id': '', + 'shares': 217, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'xxmbossma66.tuchong.com', + 'followers': 90414, + 'has_everphoto_note': false, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_959534_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '马大兴BossMa', + 'site_id': '959534', + 'type': 'user', + 'url': 'https://xxmbossma66.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '959534', + 'sites': [], + 'tags': ['色彩', '人像', '佳能', '小清新', '美女', '135mm'], + 'title': '夏日有终曲,但人生无限时。', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://xxmbossma66.tuchong.com/56875719/', + 'views': 128748 + }, + { + 'author_id': '490904', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 32, + 'content': '暖阳', + 'created_at': '2019-10-21 15:04:45', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '糖水人像小组', + '分享神仙颜值', + '拍女友才是正经事', + '青春,我爱过的那个女孩!', + '高颜值女神聚集地', + '暖色调人像' + ], + 'excerpt': '暖阳', + 'favorite_list_prefix': [], + 'favorites': 1131, + 'image_count': 6, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 603170005, + 'img_id_str': '603170005', + 'title': '280194', + 'user_id': 490904, + 'width': 998 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 653309780, + 'img_id_str': '653309780', + 'title': '280188', + 'user_id': 490904, + 'width': 998 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 311670188, + 'img_id_str': '311670188', + 'title': '280193', + 'user_id': 490904, + 'width': 998 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 523809658, + 'img_id_str': '523809658', + 'title': '280195', + 'user_id': 490904, + 'width': 998 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 353481996, + 'img_id_str': '353481996', + 'title': '280191', + 'user_id': 490904, + 'width': 998 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 272021028, + 'img_id_str': '272021028', + 'title': '280190', + 'user_id': 490904, + 'width': 998 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '32', + 'passed_time': '2019年10月21日', + 'post_id': 56552752, + 'published_at': '2019-10-21 15:04:45', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 28, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 12649, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_490904_8', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影师CAT', + 'site_id': '490904', + 'type': 'user', + 'url': 'https://tuchong.com/490904/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '490904', + 'sites': [], + 'tags': [ + '糖水人像小组', + '分享神仙颜值', + '拍女友才是正经事', + '青春,我爱过的那个女孩!', + '高颜值女神聚集地', + '暖色调人像' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/490904/56552752/', + 'views': 30412 + }, + { + 'author_id': '1579511', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 22, + 'content': 'Fade into you', + 'created_at': '2019-10-18 03:10:51', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': 'Fade into you', + 'favorite_list_prefix': [], + 'favorites': 578, + 'image_count': 15, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5830, + 'img_id': 396015089, + 'img_id_str': '396015089', + 'title': '', + 'user_id': 1579511, + 'width': 3794 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5828, + 'img_id': 431338687, + 'img_id_str': '431338687', + 'title': '', + 'user_id': 1579511, + 'width': 3891 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 223196130, + 'img_id_str': '223196130', + 'title': '', + 'user_id': 1579511, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5468, + 'img_id': 213497433, + 'img_id_str': '213497433', + 'title': '', + 'user_id': 1579511, + 'width': 3579 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4258, + 'img_id': 378975474, + 'img_id_str': '378975474', + 'title': '', + 'user_id': 1579511, + 'width': 3716 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3793, + 'img_id': 573748529, + 'img_id_str': '573748529', + 'title': '', + 'user_id': 1579511, + 'width': 5500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2753, + 'img_id': 582989229, + 'img_id_str': '582989229', + 'title': '', + 'user_id': 1579511, + 'width': 2159 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4641, + 'img_id': 68727788, + 'img_id_str': '68727788', + 'title': '', + 'user_id': 1579511, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4162, + 'img_id': 42447672, + 'img_id_str': '42447672', + 'title': '', + 'user_id': 1579511, + 'width': 3922 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5522, + 'img_id': 640202020, + 'img_id_str': '640202020', + 'title': '', + 'user_id': 1579511, + 'width': 3761 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3820, + 'img_id': 343782460, + 'img_id_str': '343782460', + 'title': '', + 'user_id': 1579511, + 'width': 5747 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4016, + 'img_id': 651867114, + 'img_id_str': '651867114', + 'title': '', + 'user_id': 1579511, + 'width': 6016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3612, + 'img_id': 462599021, + 'img_id_str': '462599021', + 'title': '', + 'user_id': 1579511, + 'width': 5328 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1760, + 'img_id': 121155967, + 'img_id_str': '121155967', + 'title': '', + 'user_id': 1579511, + 'width': 2841 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4016, + 'img_id': 210613111, + 'img_id_str': '210613111', + 'title': '', + 'user_id': 1579511, + 'width': 4631 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '22', + 'passed_time': '2019年10月18日', + 'post_id': 56194034, + 'published_at': '2019-10-18 03:10:51', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 12, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'shanelu.tuchong.com', + 'followers': 6054, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1579511_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '肖肖路', + 'site_id': '1579511', + 'type': 'user', + 'url': 'https://shanelu.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1579511', + 'sites': [], + 'tags': ['复古', '暗调', '光影', '唯美', '人像'], + 'title': 'Fade into you', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://shanelu.tuchong.com/56194034/', + 'views': 21396 + }, + { + 'author_id': '2736211', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 14, + 'content': '', + 'created_at': '2019-10-06 17:31:59', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['高颜值女神聚集地'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 617, + 'image_count': 17, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5444, + 'img_id': 304000606, + 'img_id_str': '304000606', + 'title': '', + 'user_id': 2736211, + 'width': 3378 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2983, + 'img_id': 48869335, + 'img_id_str': '48869335', + 'title': '', + 'user_id': 2736211, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3154, + 'img_id': 528133725, + 'img_id_str': '528133725', + 'title': '', + 'user_id': 2736211, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2874, + 'img_id': 353153400, + 'img_id_str': '353153400', + 'title': '', + 'user_id': 2736211, + 'width': 5726 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3438, + 'img_id': 91729487, + 'img_id_str': '91729487', + 'title': '', + 'user_id': 2736211, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2973, + 'img_id': 345681596, + 'img_id_str': '345681596', + 'title': '', + 'user_id': 2736211, + 'width': 5965 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3135, + 'img_id': 586723004, + 'img_id_str': '586723004', + 'title': '', + 'user_id': 2736211, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3187, + 'img_id': 194162534, + 'img_id_str': '194162534', + 'title': '', + 'user_id': 2736211, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 190754197, + 'img_id_str': '190754197', + 'title': '', + 'user_id': 2736211, + 'width': 3526 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3376, + 'img_id': 293645916, + 'img_id_str': '293645916', + 'title': '', + 'user_id': 2736211, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 119516500, + 'img_id_str': '119516500', + 'title': '', + 'user_id': 2736211, + 'width': 3376 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2415, + 'img_id': 458796491, + 'img_id_str': '458796491', + 'title': '', + 'user_id': 2736211, + 'width': 5200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2713, + 'img_id': 34975616, + 'img_id_str': '34975616', + 'title': '', + 'user_id': 2736211, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5022, + 'img_id': 544518100, + 'img_id_str': '544518100', + 'title': '', + 'user_id': 2736211, + 'width': 3242 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2742, + 'img_id': 190361095, + 'img_id_str': '190361095', + 'title': '', + 'user_id': 2736211, + 'width': 5757 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2931, + 'img_id': 576695928, + 'img_id_str': '576695928', + 'title': '', + 'user_id': 2736211, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2443, + 'img_id': 434548397, + 'img_id_str': '434548397', + 'title': '', + 'user_id': 2736211, + 'width': 6000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '13', + 'passed_time': '2019年10月06日', + 'post_id': 54966007, + 'published_at': '2019-10-06 17:31:59', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 27, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 2333, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2736211_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '洛洛-', + 'site_id': '2736211', + 'type': 'user', + 'url': 'https://tuchong.com/2736211/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2736211', + 'sites': [], + 'tags': ['高颜值女神聚集地', '人像', '抓拍', '美女', '写真', '胶片'], + 'title': '生活碎片', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2736211/54966007/', + 'views': 26755 + }, + { + 'author_id': '397965', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 41, + 'content': '', + 'created_at': '2019-09-01 22:23:17', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['拍女友才是正经事', '2020适马睛典'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1350, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1000, + 'img_id': 636787965, + 'img_id_str': '636787965', + 'title': '', + 'user_id': 397965, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 522296221, + 'img_id_str': '522296221', + 'title': '', + 'user_id': 397965, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1000, + 'img_id': 83467576, + 'img_id_str': '83467576', + 'title': '', + 'user_id': 397965, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1000, + 'img_id': 274439113, + 'img_id_str': '274439113', + 'title': '', + 'user_id': 397965, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 415865826, + 'img_id_str': '415865826', + 'title': '', + 'user_id': 397965, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1000, + 'img_id': 124951701, + 'img_id_str': '124951701', + 'title': '', + 'user_id': 397965, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1000, + 'img_id': 620731445, + 'img_id_str': '620731445', + 'title': '', + 'user_id': 397965, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 499948848, + 'img_id_str': '499948848', + 'title': '', + 'user_id': 397965, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 414752116, + 'img_id_str': '414752116', + 'title': '', + 'user_id': 397965, + 'width': 1000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '34', + 'passed_time': '2019年09月01日', + 'post_id': 51389145, + 'published_at': '2019-09-01 22:23:17', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '6', + 'rqt_id': '', + 'shares': 42, + 'site': { + 'description': '图虫签约摄影,QQ3365549820,微信18777169499,微博@WINGZERO1991', + 'domain': '', + 'followers': 1455, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_397965_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'WINGZERO1991', + 'site_id': '397965', + 'type': 'user', + 'url': 'https://tuchong.com/397965/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '397965', + 'sites': [], + 'tags': ['拍女友才是正经事', '2020适马睛典', '美女', '人像', '南宁', '南宁约拍'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/397965/51389145/', + 'views': 64036 + }, + { + 'author_id': '1341135', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 19, + 'content': '', + 'created_at': '2019-08-17 23:39:02', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 388, + 'image_count': 7, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3710, + 'img_id': 311595450, + 'img_id_str': '311595450', + 'title': '', + 'user_id': 1341135, + 'width': 2520 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3491, + 'img_id': 649434159, + 'img_id_str': '649434159', + 'title': '', + 'user_id': 1341135, + 'width': 2689 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5099, + 'img_id': 154374967, + 'img_id_str': '154374967', + 'title': '', + 'user_id': 1341135, + 'width': 3564 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4993, + 'img_id': 354193798, + 'img_id_str': '354193798', + 'title': '', + 'user_id': 1341135, + 'width': 3261 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4459, + 'img_id': 180261553, + 'img_id_str': '180261553', + 'title': '', + 'user_id': 1341135, + 'width': 3292 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3816, + 'img_id': 361075719, + 'img_id_str': '361075719', + 'title': '', + 'user_id': 1341135, + 'width': 3798 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2879, + 'img_id': 597660212, + 'img_id_str': '597660212', + 'title': '', + 'user_id': 1341135, + 'width': 1724 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '15', + 'passed_time': '2019年08月17日', + 'post_id': 49624838, + 'published_at': '2019-08-17 23:39:02', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 10, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 13723, + 'has_everphoto_note': false, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1341135_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '震业', + 'site_id': '1341135', + 'type': 'user', + 'url': 'https://tuchong.com/1341135/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1341135', + 'sites': [], + 'tags': ['情绪', '少女', '胶片', '尼康', '人像', '写真'], + 'title': '无法封存的记忆', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1341135/49624838/', + 'views': 24838 + }, + { + 'author_id': '1548800', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 52, + 'content': '', + 'created_at': '2019-07-28 05:55:30', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['日系集'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1352, + 'image_count': 18, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 95649634, + 'img_id_str': '95649634', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 642023166, + 'img_id_str': '642023166', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 588087039, + 'img_id_str': '588087039', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4400, + 'img_id': 445546583, + 'img_id_str': '445546583', + 'title': '001', + 'user_id': 1548800, + 'width': 4400 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4200, + 'img_id': 180715057, + 'img_id_str': '180715057', + 'title': '001', + 'user_id': 1548800, + 'width': 4200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 139558724, + 'img_id_str': '139558724', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 385384268, + 'img_id_str': '385384268', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 245726862, + 'img_id_str': '245726862', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4200, + 'img_id': 561675793, + 'img_id_str': '561675793', + 'title': '001', + 'user_id': 1548800, + 'width': 4200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 322928252, + 'img_id_str': '322928252', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 255754088, + 'img_id_str': '255754088', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 644251440, + 'img_id_str': '644251440', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 524254628, + 'img_id_str': '524254628', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 377585211, + 'img_id_str': '377585211', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 334397564, + 'img_id_str': '334397564', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 45645720, + 'img_id_str': '45645720', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 265453478, + 'img_id_str': '265453478', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 586776740, + 'img_id_str': '586776740', + 'title': '001', + 'user_id': 1548800, + 'width': 4000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '45', + 'passed_time': '2019年07月28日', + 'post_id': 46552979, + 'published_at': '2019-07-28 05:55:30', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 42, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 2007, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1548800_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影_一万', + 'site_id': '1548800', + 'type': 'user', + 'url': 'https://tuchong.com/1548800/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1548800', + 'sites': [], + 'tags': ['日系集', '人像', '色彩', '日系人像', '人'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1548800/46552979/', + 'views': 83698 + }, + { + 'author_id': '15956987', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': '', + 'created_at': '2020-01-19 11:53:09', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['谁还没点童年黑照'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 12, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 193054314, + 'img_id_str': '193054314', + 'title': '584783', + 'user_id': 15956987, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 164087695, + 'img_id_str': '164087695', + 'title': '584782', + 'user_id': 15956987, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 604358446, + 'img_id_str': '604358446', + 'title': '584780', + 'user_id': 15956987, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 391497763, + 'img_id_str': '391497763', + 'title': '584779', + 'user_id': 15956987, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 214746906, + 'img_id_str': '214746906', + 'title': '584778', + 'user_id': 15956987, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 638240249, + 'img_id_str': '638240249', + 'title': '584775', + 'user_id': 15956987, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 122734150, + 'img_id_str': '122734150', + 'title': '583391', + 'user_id': 15956987, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 134989549, + 'img_id_str': '134989549', + 'title': '583390', + 'user_id': 15956987, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 88131245, + 'img_id_str': '88131245', + 'title': '583389', + 'user_id': 15956987, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 609601613, + 'img_id_str': '609601613', + 'title': '583388', + 'user_id': 15956987, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 600492086, + 'img_id_str': '600492086', + 'title': '583387', + 'user_id': 15956987, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 341624488, + 'img_id_str': '341624488', + 'title': '584781', + 'user_id': 15956987, + 'width': 4000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月19日', + 'post_id': 61835061, + 'published_at': '2020-01-19 11:53:09', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 1, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15956987_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '李同168', + 'site_id': '15956987', + 'type': 'user', + 'url': 'https://tuchong.com/15956987/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15956987', + 'sites': [], + 'tags': ['谁还没点童年黑照', '公园', '环境'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15956987/61835061/', + 'views': 585 + }, + { + 'author_id': '15943193', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 3, + 'content': '静物光影', + 'created_at': '2020-01-18 22:39:35', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['纯粹黑白圈子'], + 'excerpt': '静物光影', + 'favorite_list_prefix': [], + 'favorites': 17, + 'image_count': 4, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3556, + 'img_id': 315410257, + 'img_id_str': '315410257', + 'title': '1254227', + 'user_id': 15943193, + 'width': 5333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2829, + 'img_id': 205768263, + 'img_id_str': '205768263', + 'title': '1254229', + 'user_id': 15943193, + 'width': 4298 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2640, + 'img_id': 123521280, + 'img_id_str': '123521280', + 'title': '1254228', + 'user_id': 15943193, + 'width': 3960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3403, + 'img_id': 562808716, + 'img_id_str': '562808716', + 'title': '1254226', + 'user_id': 15943193, + 'width': 5105 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '01月18日', + 'post_id': 61823921, + 'published_at': '2020-01-18 22:39:35', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 10, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15943193_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '楠姐1959', + 'site_id': '15943193', + 'type': 'user', + 'url': 'https://tuchong.com/15943193/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15943193', + 'sites': [], + 'tags': ['纯粹黑白圈子', '静物'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15943193/61823921/', + 'views': 471 + }, + { + 'author_id': '15946115', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '被惦记该有多好', + 'created_at': '2020-01-18 22:27:11', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['胶片人像摄影'], + 'excerpt': '被惦记该有多好', + 'favorite_list_prefix': [], + 'favorites': 14, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1080, + 'img_id': 551863811, + 'img_id_str': '551863811', + 'title': '2541999', + 'user_id': 15946115, + 'width': 1920 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月18日', + 'post_id': 61823558, + 'published_at': '2020-01-18 22:27:11', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 12, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15946115_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'DIDIL', + 'site_id': '15946115', + 'type': 'user', + 'url': 'https://tuchong.com/15946115/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15946115', + 'sites': [], + 'tags': ['胶片人像摄影'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15946115/61823558/', + 'views': 680 + }, + { + 'author_id': '15946944', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '橘子味汽水', + 'created_at': '2020-01-18 20:55:39', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '日出日落奇迹时间', + '胶片人像摄影', + '暖色调人像', + '有温度的人像', + '分享神仙颜值', + '胶片集', + '校园摄影师这样拍' + ], + 'excerpt': '橘子味汽水', + 'favorite_list_prefix': [], + 'favorites': 12, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 217368426, + 'img_id_str': '217368426', + 'title': '1733971', + 'user_id': 15946944, + 'width': 1437 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 600425986, + 'img_id_str': '600425986', + 'title': '1733968', + 'user_id': 15946944, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 493864737, + 'img_id_str': '493864737', + 'title': '1733964', + 'user_id': 15946944, + 'width': 1437 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 214550536, + 'img_id_str': '214550536', + 'title': '1733960', + 'user_id': 15946944, + 'width': 1437 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 190891765, + 'img_id_str': '190891765', + 'title': '1733955', + 'user_id': 15946944, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 62506763, + 'img_id_str': '62506763', + 'title': '1733954', + 'user_id': 15946944, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 551732989, + 'img_id_str': '551732989', + 'title': '1733952', + 'user_id': 15946944, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 306825297, + 'img_id_str': '306825297', + 'title': '1733947', + 'user_id': 15946944, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 416073484, + 'img_id_str': '416073484', + 'title': '1733942', + 'user_id': 15946944, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2001, + 'img_id': 334546752, + 'img_id_str': '334546752', + 'title': '1733940', + 'user_id': 15946944, + 'width': 1483 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 383305386, + 'img_id_str': '383305386', + 'title': '1733939', + 'user_id': 15946944, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 196331053, + 'img_id_str': '196331053', + 'title': '1733893', + 'user_id': 15946944, + 'width': 1437 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月18日', + 'post_id': 61820906, + 'published_at': '2020-01-18 20:55:39', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '个人摄影', + 'domain': '', + 'followers': 6, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15946944_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '被名字遗忘的博主', + 'site_id': '15946944', + 'type': 'user', + 'url': 'https://tuchong.com/15946944/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15946944', + 'sites': [], + 'tags': ['日出日落奇迹时间', '胶片人像摄影', '暖色调人像', '有温度的人像', '分享神仙颜值', '胶片集'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15946944/61820906/', + 'views': 699 + }, + { + 'author_id': '15951134', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '', + 'created_at': '2020-01-18 14:13:34', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['中国创意摄影'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 11, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3968, + 'img_id': 495305816, + 'img_id_str': '495305816', + 'title': '73984', + 'user_id': 15951134, + 'width': 2976 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2976, + 'img_id': 641975775, + 'img_id_str': '641975775', + 'title': '73985', + 'user_id': 15951134, + 'width': 3968 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月18日', + 'post_id': 61811635, + 'published_at': '2020-01-18 14:13:34', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 9, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15951134_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '笛声枯竭干涸', + 'site_id': '15951134', + 'type': 'user', + 'url': 'https://tuchong.com/15951134/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15951134', + 'sites': [], + 'tags': ['中国创意摄影', '博物馆', '剑', '文物'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15951134/61811635/', + 'views': 529 + }, + { + 'author_id': '372183', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 41, + 'content': '', + 'created_at': '2019-11-06 10:43:40', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['2019胶片摄影赛', '分享神仙颜值'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 760, + 'image_count': 16, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3318, + 'img_id': 604617432, + 'img_id_str': '604617432', + 'title': '', + 'user_id': 372183, + 'width': 2279 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3318, + 'img_id': 512605170, + 'img_id_str': '512605170', + 'title': '', + 'user_id': 372183, + 'width': 2279 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3318, + 'img_id': 162249271, + 'img_id_str': '162249271', + 'title': '', + 'user_id': 372183, + 'width': 2279 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3318, + 'img_id': 85899640, + 'img_id_str': '85899640', + 'title': '', + 'user_id': 372183, + 'width': 2279 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2279, + 'img_id': 557234699, + 'img_id_str': '557234699', + 'title': '', + 'user_id': 372183, + 'width': 3318 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2279, + 'img_id': 599505286, + 'img_id_str': '599505286', + 'title': '', + 'user_id': 372183, + 'width': 3318 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3318, + 'img_id': 133675638, + 'img_id_str': '133675638', + 'title': '', + 'user_id': 372183, + 'width': 2279 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3318, + 'img_id': 457620408, + 'img_id_str': '457620408', + 'title': '', + 'user_id': 372183, + 'width': 2279 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3318, + 'img_id': 38976262, + 'img_id_str': '38976262', + 'title': '', + 'user_id': 372183, + 'width': 2279 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3318, + 'img_id': 619166438, + 'img_id_str': '619166438', + 'title': '', + 'user_id': 372183, + 'width': 2279 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3318, + 'img_id': 523942565, + 'img_id_str': '523942565', + 'title': '', + 'user_id': 372183, + 'width': 2279 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3318, + 'img_id': 205896616, + 'img_id_str': '205896616', + 'title': '', + 'user_id': 372183, + 'width': 2279 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2279, + 'img_id': 486390331, + 'img_id_str': '486390331', + 'title': '', + 'user_id': 372183, + 'width': 3318 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3318, + 'img_id': 436320812, + 'img_id_str': '436320812', + 'title': '', + 'user_id': 372183, + 'width': 2279 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2193, + 'img_id': 593541550, + 'img_id_str': '593541550', + 'title': '', + 'user_id': 372183, + 'width': 3192 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2279, + 'img_id': 235584285, + 'img_id_str': '235584285', + 'title': '', + 'user_id': 372183, + 'width': 3318 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '36', + 'passed_time': '2019年11月06日', + 'post_id': 57918905, + 'published_at': '2019-11-06 10:43:40', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '7', + 'rqt_id': '', + 'shares': 24, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 11898, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_372183_7', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '摄影师暗栀', + 'site_id': '372183', + 'type': 'user', + 'url': 'https://tuchong.com/372183/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '372183', + 'sites': [], + 'tags': ['2019胶片摄影赛', '分享神仙颜值', '胶片', '写真', '夏天', '青春映像节摄影大赛'], + 'title': 'RAIN', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/372183/57918905/', + 'views': 31935 + }, + { + 'author_id': '1349328', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 61, + 'content': '在你的心里呀 在你的心底呀\n\n不管是多远的远方 不要害怕我在身旁', + 'created_at': '2019-10-10 17:40:56', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '在你的心里呀 在你的心底呀\n\n不管是多远的远方 不要害怕我在身旁', + 'favorite_list_prefix': [], + 'favorites': 2143, + 'image_count': 6, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3300, + 'img_id': 59617329, + 'img_id_str': '59617329', + 'title': '', + 'user_id': 1349328, + 'width': 2200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 549565227, + 'img_id_str': '549565227', + 'title': '', + 'user_id': 1349328, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3360, + 'img_id': 643478139, + 'img_id_str': '643478139', + 'title': '', + 'user_id': 1349328, + 'width': 2240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3360, + 'img_id': 197767343, + 'img_id_str': '197767343', + 'title': '', + 'user_id': 1349328, + 'width': 2240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 517451777, + 'img_id_str': '517451777', + 'title': '', + 'user_id': 1349328, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3104, + 'img_id': 625651990, + 'img_id_str': '625651990', + 'title': '', + 'user_id': 1349328, + 'width': 2070 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '50', + 'passed_time': '2019年10月10日', + 'post_id': 55479953, + 'published_at': '2019-10-10 17:40:56', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 63, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 7901, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1349328_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '壹梵i', + 'site_id': '1349328', + 'type': 'user', + 'url': 'https://tuchong.com/1349328/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1349328', + 'sites': [], + 'tags': ['人像', '佳能'], + 'title': '我只想做你的太阳 你的太阳', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1349328/55479953/', + 'views': 64650 + }, + { + 'author_id': '1937912', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 14, + 'content': '摄影:紫荞\n模特:cc\n地址:小鹿奔跑了摄影工作室\n—室内写真-', + 'created_at': '2019-10-08 09:08:01', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['高颜值女神聚集地'], + 'excerpt': '摄影:紫荞\n模特:cc\n地址:小鹿奔跑了摄影工作室\n—室内写真-', + 'favorite_list_prefix': [], + 'favorites': 274, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 310226554, + 'img_id_str': '310226554', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 340242406, + 'img_id_str': '340242406', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 593080710, + 'img_id_str': '593080710', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5263, + 'img_id': 451326551, + 'img_id_str': '451326551', + 'title': '001', + 'user_id': 1937912, + 'width': 3423 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4070, + 'img_id': 117878802, + 'img_id_str': '117878802', + 'title': '001', + 'user_id': 1937912, + 'width': 6140 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 643281258, + 'img_id_str': '643281258', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 408662263, + 'img_id_str': '408662263', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 574009311, + 'img_id_str': '574009311', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 176730442, + 'img_id_str': '176730442', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 402173999, + 'img_id_str': '402173999', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 48017103, + 'img_id_str': '48017103', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 642035672, + 'img_id_str': '642035672', + 'title': '001', + 'user_id': 1937912, + 'width': 4160 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '14', + 'passed_time': '2019年10月08日', + 'post_id': 55191671, + 'published_at': '2019-10-08 09:08:01', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 4, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 7144, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1937912_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '紫荞姑娘', + 'site_id': '1937912', + 'type': 'user', + 'url': 'https://tuchong.com/1937912/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1937912', + 'sites': [], + 'tags': ['高颜值女神聚集地', '人像', '书桌', '深圳约拍', '旧时光是个美人', '教室'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1937912/55191671/', + 'views': 9888 + }, + { + 'author_id': '3477512', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 51, + 'content': '', + 'created_at': '2019-09-27 16:07:44', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1250, + 'image_count': 8, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 299477022, + 'img_id_str': '299477022', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 62367761, + 'img_id_str': '62367761', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2033, + 'img_id': 443918746, + 'img_id_str': '443918746', + 'title': '001', + 'user_id': 3477512, + 'width': 3048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 274573889, + 'img_id_str': '274573889', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 570796266, + 'img_id_str': '570796266', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 200321604, + 'img_id_str': '200321604', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 498182265, + 'img_id_str': '498182265', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3048, + 'img_id': 562866193, + 'img_id_str': '562866193', + 'title': '001', + 'user_id': 3477512, + 'width': 2033 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '50', + 'passed_time': '2019年09月27日', + 'post_id': 53711715, + 'published_at': '2019-09-27 16:07:44', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 33, + 'site': { + 'description': '资深旅行摄影师', + 'domain': '', + 'followers': 44864, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3477512_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Cissy_Li', + 'site_id': '3477512', + 'type': 'user', + 'url': 'https://tuchong.com/3477512/', + 'verification_list': [ + {'verification_reason': '资深旅行摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3477512', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3477512/53711715/', + 'views': 55161 + }, + { + 'author_id': '1609287', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 72, + 'content': '老图存档', + 'created_at': '2019-09-05 09:47:37', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['分享神仙颜值'], + 'excerpt': '老图存档', + 'favorite_list_prefix': [], + 'favorites': 1486, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 631741741, + 'img_id_str': '631741741', + 'title': '', + 'user_id': 1609287, + 'width': 3200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 341352081, + 'img_id_str': '341352081', + 'title': '', + 'user_id': 1609287, + 'width': 4800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 40410275, + 'img_id_str': '40410275', + 'title': '', + 'user_id': 1609287, + 'width': 4800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 244555717, + 'img_id_str': '244555717', + 'title': '', + 'user_id': 1609287, + 'width': 4800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4700, + 'img_id': 584228741, + 'img_id_str': '584228741', + 'title': '', + 'user_id': 1609287, + 'width': 3327 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 464101158, + 'img_id_str': '464101158', + 'title': '', + 'user_id': 1609287, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 462200265, + 'img_id_str': '462200265', + 'title': '', + 'user_id': 1609287, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 388144648, + 'img_id_str': '388144648', + 'title': '', + 'user_id': 1609287, + 'width': 4800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 551133041, + 'img_id_str': '551133041', + 'title': '', + 'user_id': 1609287, + 'width': 4800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 603627113, + 'img_id_str': '603627113', + 'title': '', + 'user_id': 1609287, + 'width': 3200 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '67', + 'passed_time': '2019年09月05日', + 'post_id': 51686073, + 'published_at': '2019-09-05 09:47:37', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 92, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 69550, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1609287_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '皓子_', + 'site_id': '1609287', + 'type': 'user', + 'url': 'https://tuchong.com/1609287/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1609287', + 'sites': [], + 'tags': ['分享神仙颜值', '人像', '色彩', '女神', 'JK', '私影'], + 'title': '这是一场 蓝色的 关于海洋的梦', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1609287/51686073/', + 'views': 53256 + }, + { + 'author_id': '406880', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 58, + 'content': '又是一波预告,哈哈哈?', + 'created_at': '2019-11-01 21:20:13', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '又是一波预告,哈哈哈?', + 'favorite_list_prefix': [], + 'favorites': 776, + 'image_count': 3, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 510507203, + 'img_id_str': '510507203', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 513587249, + 'img_id_str': '513587249', + 'title': '001', + 'user_id': 406880, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 122403027, + 'img_id_str': '122403027', + 'title': '001', + 'user_id': 406880, + 'width': 960 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '54', + 'passed_time': '2019年11月01日', + 'post_id': 57521816, + 'published_at': '2019-11-01 21:20:13', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 23, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 24454, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_406880_9', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '风-vision', + 'site_id': '406880', + 'type': 'user', + 'url': 'https://tuchong.com/406880/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '406880', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/406880/57521816/', + 'views': 31253 + }, + { + 'author_id': '58885', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 42, + 'content': '彼岸的光永远也照不进遥远的森林里', + 'created_at': '2019-10-28 17:46:39', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '彼岸的光永远也照不进遥远的森林里', + 'favorite_list_prefix': [], + 'favorites': 1045, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 85309505, + 'img_id_str': '85309505', + 'title': '', + 'user_id': 58885, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 39499918, + 'img_id_str': '39499918', + 'title': '', + 'user_id': 58885, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 222410923, + 'img_id_str': '222410923', + 'title': '', + 'user_id': 58885, + 'width': 1333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 330020981, + 'img_id_str': '330020981', + 'title': '', + 'user_id': 58885, + 'width': 1333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 591639939, + 'img_id_str': '591639939', + 'title': '', + 'user_id': 58885, + 'width': 1333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 400865260, + 'img_id_str': '400865260', + 'title': '', + 'user_id': 58885, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 451328623, + 'img_id_str': '451328623', + 'title': '', + 'user_id': 58885, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 475576830, + 'img_id_str': '475576830', + 'title': '', + 'user_id': 58885, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 478787916, + 'img_id_str': '478787916', + 'title': '', + 'user_id': 58885, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 94418445, + 'img_id_str': '94418445', + 'title': '', + 'user_id': 58885, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 92452676, + 'img_id_str': '92452676', + 'title': '', + 'user_id': 58885, + 'width': 2000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '35', + 'passed_time': '2019年10月28日', + 'post_id': 57184774, + 'published_at': '2019-10-28 17:46:39', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 31, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'somy.tuchong.com', + 'followers': 21627, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_58885_3', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '骨灰级烧卖', + 'site_id': '58885', + 'type': 'user', + 'url': 'https://somy.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '58885', + 'sites': [], + 'tags': ['色彩', '人像', '美女'], + 'title': '彼岸的光', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://somy.tuchong.com/57184774/', + 'views': 36050 + }, + { + 'author_id': '3591420', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 8, + 'content': '', + 'created_at': '2019-10-10 00:39:21', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['杭州摄影爱好者'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 312, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1806, + 'img_id': 335458806, + 'img_id_str': '335458806', + 'title': '', + 'user_id': 3591420, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 150974551, + 'img_id_str': '150974551', + 'title': '', + 'user_id': 3591420, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 512864494, + 'img_id_str': '512864494', + 'title': '', + 'user_id': 3591420, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 270774399, + 'img_id_str': '270774399', + 'title': '', + 'user_id': 3591420, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1755, + 'img_id': 427864865, + 'img_id_str': '427864865', + 'title': '', + 'user_id': 3591420, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6092, + 'img_id': 543404108, + 'img_id_str': '543404108', + 'title': '', + 'user_id': 3591420, + 'width': 4036 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4022, + 'img_id': 41529519, + 'img_id_str': '41529519', + 'title': '', + 'user_id': 3591420, + 'width': 6034 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3738, + 'img_id': 350990284, + 'img_id_str': '350990284', + 'title': '', + 'user_id': 3591420, + 'width': 6240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 621064250, + 'img_id_str': '621064250', + 'title': '', + 'user_id': 3591420, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3751, + 'img_id': 417705587, + 'img_id_str': '417705587', + 'title': '', + 'user_id': 3591420, + 'width': 5609 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4160, + 'img_id': 509194485, + 'img_id_str': '509194485', + 'title': '', + 'user_id': 3591420, + 'width': 6240 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '8', + 'passed_time': '2019年10月10日', + 'post_id': 55422924, + 'published_at': '2019-10-10 00:39:21', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 12, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 810, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3591420_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '沐生阿', + 'site_id': '3591420', + 'type': 'user', + 'url': 'https://tuchong.com/3591420/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3591420', + 'sites': [], + 'tags': ['杭州摄影爱好者', '人像', '少女', '情绪', '日系', '和服'], + 'title': '吾蕊—', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3591420/55422924/', + 'views': 9804 + }, + { + 'author_id': '1670980', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 129, + 'content': '', + 'created_at': '2019-09-10 10:16:57', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 2233, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 207855882, + 'img_id_str': '207855882', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 66035577, + 'img_id_str': '66035577', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 535797870, + 'img_id_str': '535797870', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 366518384, + 'img_id_str': '366518384', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 85827788, + 'img_id_str': '85827788', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 246260128, + 'img_id_str': '246260128', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 291938562, + 'img_id_str': '291938562', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 476946558, + 'img_id_str': '476946558', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 429694972, + 'img_id_str': '429694972', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 338665718, + 'img_id_str': '338665718', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 76717721, + 'img_id_str': '76717721', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '104', + 'passed_time': '2019年09月10日', + 'post_id': 52159682, + 'published_at': '2019-09-10 10:16:57', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 131, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 12358, + 'has_everphoto_note': false, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1670980_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '沐秋-王伟', + 'site_id': '1670980', + 'type': 'user', + 'url': 'https://tuchong.com/1670980/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1670980', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1670980/52159682/', + 'views': 158353 + }, + { + 'author_id': '2717528', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 36, + 'content': '', + 'created_at': '2019-09-04 19:11:13', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['我们都爱日系摄影'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1110, + 'image_count': 34, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 96640479, + 'img_id_str': '96640479', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1680, + 'img_id': 166895241, + 'img_id_str': '166895241', + 'title': '001', + 'user_id': 2717528, + 'width': 2520 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 640065066, + 'img_id_str': '640065066', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 641441323, + 'img_id_str': '641441323', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1680, + 'img_id': 632790316, + 'img_id_str': '632790316', + 'title': '001', + 'user_id': 2717528, + 'width': 2520 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 583507461, + 'img_id_str': '583507461', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 94411886, + 'img_id_str': '94411886', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 507354915, + 'img_id_str': '507354915', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 290365003, + 'img_id_str': '290365003', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 461872818, + 'img_id_str': '461872818', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 555326989, + 'img_id_str': '555326989', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 351969024, + 'img_id_str': '351969024', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 141794721, + 'img_id_str': '141794721', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1260, + 'img_id': 56860340, + 'img_id_str': '56860340', + 'title': '001', + 'user_id': 2717528, + 'width': 840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 489070061, + 'img_id_str': '489070061', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 598252851, + 'img_id_str': '598252851', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 68394770, + 'img_id_str': '68394770', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 57712296, + 'img_id_str': '57712296', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 432251019, + 'img_id_str': '432251019', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 630234687, + 'img_id_str': '630234687', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1680, + 'img_id': 194485713, + 'img_id_str': '194485713', + 'title': '001', + 'user_id': 2717528, + 'width': 2520 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 278306572, + 'img_id_str': '278306572', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 568434138, + 'img_id_str': '568434138', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 164601139, + 'img_id_str': '164601139', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 405773768, + 'img_id_str': '405773768', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 192453919, + 'img_id_str': '192453919', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 311664243, + 'img_id_str': '311664243', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1680, + 'img_id': 223452545, + 'img_id_str': '223452545', + 'title': '001', + 'user_id': 2717528, + 'width': 2520 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 463642088, + 'img_id_str': '463642088', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 153001409, + 'img_id_str': '153001409', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 613916501, + 'img_id_str': '613916501', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1649, + 'img_id': 375955091, + 'img_id_str': '375955091', + 'title': '001', + 'user_id': 2717528, + 'width': 2474 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 165322085, + 'img_id_str': '165322085', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2520, + 'img_id': 455646831, + 'img_id_str': '455646831', + 'title': '001', + 'user_id': 2717528, + 'width': 1680 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '33', + 'passed_time': '2019年09月04日', + 'post_id': 51641574, + 'published_at': '2019-09-04 19:11:13', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 24, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 11801, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2717528_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '卡巴拉岛的回忆', + 'site_id': '2717528', + 'type': 'user', + 'url': 'https://tuchong.com/2717528/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2717528', + 'sites': [], + 'tags': ['我们都爱日系摄影', '人像', '色彩', '体操服'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2717528/51641574/', + 'views': 48484 + }, + { + 'author_id': '1531311', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 28, + 'content': '已经迫不及待的期盼着秋天的到来,先将画面改成秋天,金灿灿的秋色!\nwb:@王艺萌-\nvx:wyx_photo', + 'created_at': '2019-09-04 15:10:16', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '已经迫不及待的期盼着秋天的到来,先将画面改成秋天,金灿灿的秋色!\nwb:@王艺萌-\nvx:wyx_photo', + 'favorite_list_prefix': [], + 'favorites': 414, + 'image_count': 20, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 130981488, + 'img_id_str': '130981488', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2048, + 'img_id': 428449366, + 'img_id_str': '428449366', + 'title': '', + 'user_id': 1531311, + 'width': 3072 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 235904667, + 'img_id_str': '235904667', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 615686185, + 'img_id_str': '615686185', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 526688126, + 'img_id_str': '526688126', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 428056516, + 'img_id_str': '428056516', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 616275551, + 'img_id_str': '616275551', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 389783397, + 'img_id_str': '389783397', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 390766432, + 'img_id_str': '390766432', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 389062097, + 'img_id_str': '389062097', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 344038612, + 'img_id_str': '344038612', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 246455866, + 'img_id_str': '246455866', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 574332460, + 'img_id_str': '574332460', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 521182670, + 'img_id_str': '521182670', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 456301950, + 'img_id_str': '456301950', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2048, + 'img_id': 628923951, + 'img_id_str': '628923951', + 'title': '', + 'user_id': 1531311, + 'width': 3072 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2048, + 'img_id': 240361122, + 'img_id_str': '240361122', + 'title': '', + 'user_id': 1531311, + 'width': 3072 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 644259478, + 'img_id_str': '644259478', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 521379118, + 'img_id_str': '521379118', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2048, + 'img_id': 34905340, + 'img_id_str': '34905340', + 'title': '', + 'user_id': 1531311, + 'width': 3072 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '28', + 'passed_time': '2019年09月04日', + 'post_id': 51624155, + 'published_at': '2019-09-04 15:10:16', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 20, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'wangyimeng.tuchong.com', + 'followers': 11125, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1531311_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '王艺萌', + 'site_id': '1531311', + 'type': 'user', + 'url': 'https://wangyimeng.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1531311', + 'sites': [], + 'tags': ['小清新', '日系', '校园', '重庆', '少女'], + 'title': '「秋之季」', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://wangyimeng.tuchong.com/51624155/', + 'views': 16081 + }, + { + 'author_id': '1345449', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 66, + 'content': '', + 'created_at': '2019-08-31 18:37:10', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 835, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 8283, + 'img_id': 607821143, + 'img_id_str': '607821143', + 'title': '001', + 'user_id': 1345449, + 'width': 5197 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8248, + 'img_id': 158833669, + 'img_id_str': '158833669', + 'title': '001', + 'user_id': 1345449, + 'width': 5197 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3917, + 'img_id': 501193426, + 'img_id_str': '501193426', + 'title': '001', + 'user_id': 1345449, + 'width': 5197 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8271, + 'img_id': 388144118, + 'img_id_str': '388144118', + 'title': '001', + 'user_id': 1345449, + 'width': 5197 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8037, + 'img_id': 75733994, + 'img_id_str': '75733994', + 'title': '001', + 'user_id': 1345449, + 'width': 5197 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7989, + 'img_id': 383819270, + 'img_id_str': '383819270', + 'title': '001', + 'user_id': 1345449, + 'width': 5197 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8311, + 'img_id': 161520882, + 'img_id_str': '161520882', + 'title': '001', + 'user_id': 1345449, + 'width': 5197 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8236, + 'img_id': 107257258, + 'img_id_str': '107257258', + 'title': '001', + 'user_id': 1345449, + 'width': 5197 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8184, + 'img_id': 561683249, + 'img_id_str': '561683249', + 'title': '001', + 'user_id': 1345449, + 'width': 5197 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '65', + 'passed_time': '2019年08月31日', + 'post_id': 51241630, + 'published_at': '2019-08-31 18:37:10', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 25, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 9229, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1345449_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '钟思杰', + 'site_id': '1345449', + 'type': 'user', + 'url': 'https://tuchong.com/1345449/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1345449', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1345449/51241630/', + 'views': 49242 + }, + { + 'author_id': '1670980', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 15, + 'content': '', + 'created_at': '2019-08-07 10:17:20', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 537, + 'image_count': 7, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 561940355, + 'img_id_str': '561940355', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 552568829, + 'img_id_str': '552568829', + 'title': '', + 'user_id': 1670980, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 301041608, + 'img_id_str': '301041608', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 566003884, + 'img_id_str': '566003884', + 'title': '', + 'user_id': 1670980, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 339773496, + 'img_id_str': '339773496', + 'title': '', + 'user_id': 1670980, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 465078232, + 'img_id_str': '465078232', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 156797479, + 'img_id_str': '156797479', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '15', + 'passed_time': '2019年08月07日', + 'post_id': 48037445, + 'published_at': '2019-08-07 10:17:20', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 8, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 12358, + 'has_everphoto_note': false, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1670980_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '沐秋-王伟', + 'site_id': '1670980', + 'type': 'user', + 'url': 'https://tuchong.com/1670980/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1670980', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1670980/48037445/', + 'views': 33698 + }, + { + 'author_id': '344885', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 41, + 'content': '', + 'created_at': '2019-12-07 18:50:19', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 917, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 243319798, + 'img_id_str': '243319798', + 'title': '', + 'user_id': 344885, + 'width': 4002 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 515490449, + 'img_id_str': '515490449', + 'title': '', + 'user_id': 344885, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 91013501, + 'img_id_str': '91013501', + 'title': '', + 'user_id': 344885, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 42582414, + 'img_id_str': '42582414', + 'title': '', + 'user_id': 344885, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 210027059, + 'img_id_str': '210027059', + 'title': '', + 'user_id': 344885, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 360825728, + 'img_id_str': '360825728', + 'title': '', + 'user_id': 344885, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 164675845, + 'img_id_str': '164675845', + 'title': '', + 'user_id': 344885, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 217891588, + 'img_id_str': '217891588', + 'title': '', + 'user_id': 344885, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 222872283, + 'img_id_str': '222872283', + 'title': '', + 'user_id': 344885, + 'width': 5304 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '39', + 'passed_time': '2019年12月07日', + 'post_id': 60040503, + 'published_at': '2019-12-07 18:50:19', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 48, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'alstonchan.tuchong.com', + 'followers': 12812, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_344885_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Alston-西瓜呆毛汪', + 'site_id': '344885', + 'type': 'user', + 'url': 'https://alstonchan.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '344885', + 'sites': [], + 'tags': ['色彩', '人像', '小清新', '美女', '日系', 'Cosplay'], + 'title': '天火泳衣COSPLAY', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://alstonchan.tuchong.com/60040503/', + 'views': 35149 + }, + { + 'author_id': '1107388', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 47, + 'content': '初恋', + 'created_at': '2019-11-28 10:53:41', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '初恋', + 'favorite_list_prefix': [], + 'favorites': 544, + 'image_count': 14, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3450, + 'img_id': 584893019, + 'img_id_str': '584893019', + 'title': '001', + 'user_id': 1107388, + 'width': 2300 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3450, + 'img_id': 45203660, + 'img_id_str': '45203660', + 'title': '001', + 'user_id': 1107388, + 'width': 2300 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3450, + 'img_id': 301973729, + 'img_id_str': '301973729', + 'title': '001', + 'user_id': 1107388, + 'width': 2300 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3450, + 'img_id': 411877727, + 'img_id_str': '411877727', + 'title': '001', + 'user_id': 1107388, + 'width': 2300 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3450, + 'img_id': 408404567, + 'img_id_str': '408404567', + 'title': '001', + 'user_id': 1107388, + 'width': 2300 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3450, + 'img_id': 88326320, + 'img_id_str': '88326320', + 'title': '001', + 'user_id': 1107388, + 'width': 2300 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2133, + 'img_id': 101105727, + 'img_id_str': '101105727', + 'title': '001', + 'user_id': 1107388, + 'width': 3200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3450, + 'img_id': 118603819, + 'img_id_str': '118603819', + 'title': '001', + 'user_id': 1107388, + 'width': 2300 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2333, + 'img_id': 256885160, + 'img_id_str': '256885160', + 'title': '001', + 'user_id': 1107388, + 'width': 3500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3450, + 'img_id': 92061729, + 'img_id_str': '92061729', + 'title': '001', + 'user_id': 1107388, + 'width': 2300 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2333, + 'img_id': 184860971, + 'img_id_str': '184860971', + 'title': '001', + 'user_id': 1107388, + 'width': 3500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3450, + 'img_id': 344375883, + 'img_id_str': '344375883', + 'title': '001', + 'user_id': 1107388, + 'width': 2300 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3450, + 'img_id': 431407184, + 'img_id_str': '431407184', + 'title': '001', + 'user_id': 1107388, + 'width': 2300 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2333, + 'img_id': 408731758, + 'img_id_str': '408731758', + 'title': '001', + 'user_id': 1107388, + 'width': 3500 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '41', + 'passed_time': '2019年11月28日', + 'post_id': 59482620, + 'published_at': '2019-11-28 10:53:41', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 23, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 2765, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1107388_6', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '太阳酱Catnip', + 'site_id': '1107388', + 'type': 'user', + 'url': 'https://tuchong.com/1107388/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1107388', + 'sites': [], + 'tags': ['可爱', '女孩', '人像写真', '日系小清新', '甜美', '贵阳约拍'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1107388/59482620/', + 'views': 16812 + }, + { + 'author_id': '1827482', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 85, + 'content': 'She blushed', + 'created_at': '2019-09-21 20:16:22', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['日出日落奇迹时间', '2019胶片摄影赛'], + 'excerpt': 'She blushed', + 'favorite_list_prefix': [], + 'favorites': 3257, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 551135050, + 'img_id_str': '551135050', + 'title': '', + 'user_id': 1827482, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 428647896, + 'img_id_str': '428647896', + 'title': '', + 'user_id': 1827482, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 101950865, + 'img_id_str': '101950865', + 'title': '', + 'user_id': 1827482, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 408593604, + 'img_id_str': '408593604', + 'title': '', + 'user_id': 1827482, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 179349038, + 'img_id_str': '179349038', + 'title': '', + 'user_id': 1827482, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 139699621, + 'img_id_str': '139699621', + 'title': '', + 'user_id': 1827482, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 462268252, + 'img_id_str': '462268252', + 'title': '', + 'user_id': 1827482, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 165062003, + 'img_id_str': '165062003', + 'title': '', + 'user_id': 1827482, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 512009538, + 'img_id_str': '512009538', + 'title': '', + 'user_id': 1827482, + 'width': 2000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '72', + 'passed_time': '2019年09月21日', + 'post_id': 53196603, + 'published_at': '2019-09-21 20:16:22', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 84, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'j-leonardo.tuchong.com', + 'followers': 6303, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1827482_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '空镜Titanium', + 'site_id': '1827482', + 'type': 'user', + 'url': 'https://j-leonardo.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1827482', + 'sites': [], + 'tags': ['日出日落奇迹时间', '2019胶片摄影赛', '人像', '胶片', '色彩', '复古'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://j-leonardo.tuchong.com/53196603/', + 'views': 160455 + }, + { + 'author_id': '340199', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 10, + 'content': '宫', + 'created_at': '2019-12-18 10:49:11', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '宫', + 'favorite_list_prefix': [], + 'favorites': 212, + 'image_count': 24, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 289129669, + 'img_id_str': '289129669', + 'title': '001', + 'user_id': 340199, + 'width': 4512 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 96978052, + 'img_id_str': '96978052', + 'title': '001', + 'user_id': 340199, + 'width': 3024 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 152290603, + 'img_id_str': '152290603', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 456639653, + 'img_id_str': '456639653', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 393528212, + 'img_id_str': '393528212', + 'title': '001', + 'user_id': 340199, + 'width': 3024 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 209699905, + 'img_id_str': '209699905', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 488752286, + 'img_id_str': '488752286', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 254002056, + 'img_id_str': '254002056', + 'title': '001', + 'user_id': 340199, + 'width': 3024 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 503038851, + 'img_id_str': '503038851', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 42321020, + 'img_id_str': '42321020', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 205308642, + 'img_id_str': '205308642', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 99796019, + 'img_id_str': '99796019', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 613073677, + 'img_id_str': '613073677', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 558679458, + 'img_id_str': '558679458', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 396739814, + 'img_id_str': '396739814', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 278840362, + 'img_id_str': '278840362', + 'title': '001', + 'user_id': 340199, + 'width': 3024 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 526369980, + 'img_id_str': '526369980', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 345359459, + 'img_id_str': '345359459', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 506643573, + 'img_id_str': '506643573', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 151831963, + 'img_id_str': '151831963', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 312788302, + 'img_id_str': '312788302', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 399688592, + 'img_id_str': '399688592', + 'title': '001', + 'user_id': 340199, + 'width': 4512 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 600818395, + 'img_id_str': '600818395', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 99664817, + 'img_id_str': '99664817', + 'title': '001', + 'user_id': 340199, + 'width': 2448 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '10', + 'passed_time': '2019年12月18日', + 'post_id': 60634988, + 'published_at': '2019-12-18 10:49:11', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 22, + 'site': { + 'description': '2017年iPhone摄影大赛(IPPAWARDS)年度最佳摄影师”和“日落组金奖”获得者', + 'domain': '', + 'followers': 15635, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_340199_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '张匡龙', + 'site_id': '340199', + 'type': 'user', + 'url': 'https://tuchong.com/340199/', + 'verification_list': [ + { + 'verification_reason': + '2017年iPhone摄影大赛(IPPAWARDS)年度最佳摄影师”和“日落组金奖”获得者', + 'verification_type': 12 + } + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '340199', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/340199/60634988/', + 'views': 10851 + }, + { + 'author_id': '4617804', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 24, + 'content': '仲夏夜之梦', + 'created_at': '2019-08-19 23:37:18', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['分享神仙颜值'], + 'excerpt': '仲夏夜之梦', + 'favorite_list_prefix': [], + 'favorites': 507, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 562861115, + 'img_id_str': '562861115', + 'title': '001', + 'user_id': 4617804, + 'width': 4406 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5632, + 'img_id': 387552491, + 'img_id_str': '387552491', + 'title': '001', + 'user_id': 4617804, + 'width': 3952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6624, + 'img_id': 294359977, + 'img_id_str': '294359977', + 'title': '001', + 'user_id': 4617804, + 'width': 4385 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6609, + 'img_id': 173053391, + 'img_id_str': '173053391', + 'title': '001', + 'user_id': 4617804, + 'width': 4406 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 149918781, + 'img_id_str': '149918781', + 'title': '001', + 'user_id': 4617804, + 'width': 6603 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6167, + 'img_id': 385913749, + 'img_id_str': '385913749', + 'title': '001', + 'user_id': 4617804, + 'width': 4395 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 506696597, + 'img_id_str': '506696597', + 'title': '001', + 'user_id': 4617804, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 578000045, + 'img_id_str': '578000045', + 'title': '001', + 'user_id': 4617804, + 'width': 4343 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5849, + 'img_id': 415077243, + 'img_id_str': '415077243', + 'title': '001', + 'user_id': 4617804, + 'width': 4115 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7956, + 'img_id': 599167956, + 'img_id_str': '599167956', + 'title': '001', + 'user_id': 4617804, + 'width': 5193 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6719, + 'img_id': 387683006, + 'img_id_str': '387683006', + 'title': '001', + 'user_id': 4617804, + 'width': 4374 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '18', + 'passed_time': '2019年08月19日', + 'post_id': 49895454, + 'published_at': '2019-08-19 23:37:18', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '2', + 'rqt_id': '', + 'shares': 22, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 4859, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_4617804_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '曹小co', + 'site_id': '4617804', + 'type': 'user', + 'url': 'https://tuchong.com/4617804/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '4617804', + 'sites': [], + 'tags': ['分享神仙颜值', '人像', '复古', '美女', '情绪', '女神'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/4617804/49895454/', + 'views': 19395 + }, + { + 'author_id': '1670980', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 7, + 'content': '', + 'created_at': '2019-10-07 11:37:02', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 297, + 'image_count': 8, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 651014278, + 'img_id_str': '651014278', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 483897811, + 'img_id_str': '483897811', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 319467659, + 'img_id_str': '319467659', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 560901726, + 'img_id_str': '560901726', + 'title': '', + 'user_id': 1670980, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 449884277, + 'img_id_str': '449884277', + 'title': '', + 'user_id': 1670980, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 390377090, + 'img_id_str': '390377090', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 455651299, + 'img_id_str': '455651299', + 'title': '', + 'user_id': 1670980, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 635678800, + 'img_id_str': '635678800', + 'title': '', + 'user_id': 1670980, + 'width': 5760 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '6', + 'passed_time': '2019年10月07日', + 'post_id': 55072749, + 'published_at': '2019-10-07 11:37:02', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 9, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 12358, + 'has_everphoto_note': false, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1670980_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '沐秋-王伟', + 'site_id': '1670980', + 'type': 'user', + 'url': 'https://tuchong.com/1670980/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1670980', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1670980/55072749/', + 'views': 15964 + }, + { + 'author_id': '959534', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 38, + 'content': '我的人像摄影教学已经开始招生,咨询报名请加vx:18310080899或majingbossma', + 'created_at': '2019-12-28 16:14:13', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '我的人像摄影教学已经开始招生,咨询报名请加vx:18310080899或majingbossma', + 'favorite_list_prefix': [], + 'favorites': 1039, + 'image_count': 13, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 324846727, + 'img_id_str': '324846727', + 'title': '', + 'user_id': 959534, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 615368312, + 'img_id_str': '615368312', + 'title': '', + 'user_id': 959534, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5551, + 'img_id': 278840649, + 'img_id_str': '278840649', + 'title': '', + 'user_id': 959534, + 'width': 3700 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 444449905, + 'img_id_str': '444449905', + 'title': '', + 'user_id': 959534, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 45139246, + 'img_id_str': '45139246', + 'title': '', + 'user_id': 959534, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 512214496, + 'img_id_str': '512214496', + 'title': '', + 'user_id': 959534, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4701, + 'img_id': 653772065, + 'img_id_str': '653772065', + 'title': '', + 'user_id': 959534, + 'width': 3133 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5668, + 'img_id': 487048347, + 'img_id_str': '487048347', + 'title': '', + 'user_id': 959534, + 'width': 3779 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 293914563, + 'img_id_str': '293914563', + 'title': '', + 'user_id': 959534, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 194168337, + 'img_id_str': '194168337', + 'title': '', + 'user_id': 959534, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 153011503, + 'img_id_str': '153011503', + 'title': '', + 'user_id': 959534, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 110806428, + 'img_id_str': '110806428', + 'title': '', + 'user_id': 959534, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3557, + 'img_id': 621921882, + 'img_id_str': '621921882', + 'title': '', + 'user_id': 959534, + 'width': 5335 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '36', + 'passed_time': '2019年12月28日', + 'post_id': 61095909, + 'published_at': '2019-12-28 16:14:13', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 32, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'xxmbossma66.tuchong.com', + 'followers': 90414, + 'has_everphoto_note': false, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_959534_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '马大兴BossMa', + 'site_id': '959534', + 'type': 'user', + 'url': 'https://xxmbossma66.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '959534', + 'sites': [], + 'tags': ['色彩', '人像', '佳能', '小清新', '美女', '壁纸'], + 'title': 'I Wanna Be Your Christmas', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://xxmbossma66.tuchong.com/61095909/', + 'views': 32221 + }, + { + 'author_id': '1339745', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 8, + 'content': '', + 'created_at': '2019-08-02 21:22:53', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['拍女友才是正经事'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 585, + 'image_count': 7, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 460424317, + 'img_id_str': '460424317', + 'title': '', + 'user_id': 1339745, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 220955968, + 'img_id_str': '220955968', + 'title': '', + 'user_id': 1339745, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6449, + 'img_id': 550143135, + 'img_id_str': '550143135', + 'title': '', + 'user_id': 1339745, + 'width': 4374 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 345670890, + 'img_id_str': '345670890', + 'title': '', + 'user_id': 1339745, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 559908423, + 'img_id_str': '559908423', + 'title': '', + 'user_id': 1339745, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 604275538, + 'img_id_str': '604275538', + 'title': '', + 'user_id': 1339745, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 136414135, + 'img_id_str': '136414135', + 'title': '', + 'user_id': 1339745, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '8', + 'passed_time': '2019年08月02日', + 'post_id': 47393653, + 'published_at': '2019-08-02 21:22:53', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 8, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'meetanan.tuchong.com', + 'followers': 24876, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1339745_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '居里安安Alana', + 'site_id': '1339745', + 'type': 'user', + 'url': 'https://meetanan.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1339745', + 'sites': [], + 'tags': ['拍女友才是正经事', '人像', '写真', '少女'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://meetanan.tuchong.com/47393653/', + 'views': 14793 + }, + { + 'author_id': '1248442', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 47, + 'content': '', + 'created_at': '2019-12-18 21:24:39', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 483, + 'image_count': 24, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3086, + 'img_id': 206750429, + 'img_id_str': '206750429', + 'title': '', + 'user_id': 1248442, + 'width': 2068 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3261, + 'img_id': 45729285, + 'img_id_str': '45729285', + 'title': '', + 'user_id': 1248442, + 'width': 2184 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3366, + 'img_id': 552453204, + 'img_id_str': '552453204', + 'title': '', + 'user_id': 1248442, + 'width': 2255 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3366, + 'img_id': 196199348, + 'img_id_str': '196199348', + 'title': '', + 'user_id': 1248442, + 'width': 2255 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2145, + 'img_id': 218350670, + 'img_id_str': '218350670', + 'title': '', + 'user_id': 1248442, + 'width': 1437 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3366, + 'img_id': 252953434, + 'img_id_str': '252953434', + 'title': '', + 'user_id': 1248442, + 'width': 2255 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1996, + 'img_id': 110019582, + 'img_id_str': '110019582', + 'title': '', + 'user_id': 1248442, + 'width': 2980 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3366, + 'img_id': 574604187, + 'img_id_str': '574604187', + 'title': '', + 'user_id': 1248442, + 'width': 2255 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3366, + 'img_id': 397854052, + 'img_id_str': '397854052', + 'title': '', + 'user_id': 1248442, + 'width': 2255 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2897, + 'img_id': 549110899, + 'img_id_str': '549110899', + 'title': '', + 'user_id': 1248442, + 'width': 1941 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 211404170, + 'img_id_str': '211404170', + 'title': '', + 'user_id': 1248442, + 'width': 1929 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2522, + 'img_id': 265536672, + 'img_id_str': '265536672', + 'title': '', + 'user_id': 1248442, + 'width': 1690 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2900, + 'img_id': 35571625, + 'img_id_str': '35571625', + 'title': '', + 'user_id': 1248442, + 'width': 1943 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2690, + 'img_id': 76006448, + 'img_id_str': '76006448', + 'title': '', + 'user_id': 1248442, + 'width': 1802 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3301, + 'img_id': 158582015, + 'img_id_str': '158582015', + 'title': '', + 'user_id': 1248442, + 'width': 2212 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3366, + 'img_id': 591250899, + 'img_id_str': '591250899', + 'title': '', + 'user_id': 1248442, + 'width': 2255 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2766, + 'img_id': 642302650, + 'img_id_str': '642302650', + 'title': '', + 'user_id': 1248442, + 'width': 1853 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3284, + 'img_id': 328320277, + 'img_id_str': '328320277', + 'title': '', + 'user_id': 1248442, + 'width': 2200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3366, + 'img_id': 444056272, + 'img_id_str': '444056272', + 'title': '', + 'user_id': 1248442, + 'width': 2255 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3094, + 'img_id': 387564941, + 'img_id_str': '387564941', + 'title': '', + 'user_id': 1248442, + 'width': 2073 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3366, + 'img_id': 86688677, + 'img_id_str': '86688677', + 'title': '', + 'user_id': 1248442, + 'width': 2255 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3366, + 'img_id': 181060569, + 'img_id_str': '181060569', + 'title': '', + 'user_id': 1248442, + 'width': 2255 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3366, + 'img_id': 160220456, + 'img_id_str': '160220456', + 'title': '', + 'user_id': 1248442, + 'width': 2255 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2498, + 'img_id': 418235981, + 'img_id_str': '418235981', + 'title': '', + 'user_id': 1248442, + 'width': 2224 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '27', + 'passed_time': '2019年12月18日', + 'post_id': 60663134, + 'published_at': '2019-12-18 21:24:39', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '3', + 'rqt_id': '', + 'shares': 17, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 5916, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1248442_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Z同学同学', + 'site_id': '1248442', + 'type': 'user', + 'url': 'https://tuchong.com/1248442/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1248442', + 'sites': [], + 'tags': ['色彩', '人像', '胶片', '小清新', '美女', '50mm'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1248442/60663134/', + 'views': 28945 + }, + { + 'author_id': '479878', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 21, + 'content': '', + 'created_at': '2019-10-19 22:30:22', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['第三届京东摄影金像奖', '2019你最满意的照片'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 389, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5952, + 'img_id': 368555141, + 'img_id_str': '368555141', + 'title': '', + 'user_id': 479878, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5952, + 'img_id': 373470362, + 'img_id_str': '373470362', + 'title': '', + 'user_id': 479878, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5505, + 'img_id': 523219730, + 'img_id_str': '523219730', + 'title': '', + 'user_id': 479878, + 'width': 4144 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5952, + 'img_id': 454014208, + 'img_id_str': '454014208', + 'title': '', + 'user_id': 479878, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5952, + 'img_id': 265270379, + 'img_id_str': '265270379', + 'title': '', + 'user_id': 479878, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5952, + 'img_id': 215070072, + 'img_id_str': '215070072', + 'title': '', + 'user_id': 479878, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5952, + 'img_id': 471970807, + 'img_id_str': '471970807', + 'title': '', + 'user_id': 479878, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5952, + 'img_id': 521319377, + 'img_id_str': '521319377', + 'title': '', + 'user_id': 479878, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5952, + 'img_id': 376157322, + 'img_id_str': '376157322', + 'title': '', + 'user_id': 479878, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5952, + 'img_id': 240759543, + 'img_id_str': '240759543', + 'title': '', + 'user_id': 479878, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 487043980, + 'img_id_str': '487043980', + 'title': '', + 'user_id': 479878, + 'width': 5952 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '21', + 'passed_time': '2019年10月19日', + 'post_id': 56381172, + 'published_at': '2019-10-19 22:30:22', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 10, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 15826, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_479878_8', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影师山木', + 'site_id': '479878', + 'type': 'user', + 'url': 'https://tuchong.com/479878/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '479878', + 'sites': [], + 'tags': ['第三届京东摄影金像奖', '2019你最满意的照片', '人像', '少女', '情绪', '写真'], + 'title': '勿念', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/479878/56381172/', + 'views': 10053 + }, + { + 'author_id': '14218199', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 10, + 'content': '', + 'created_at': '2019-08-31 00:00:55', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 2019, + 'image_count': 7, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1366, + 'img_id': 117677308, + 'img_id_str': '117677308', + 'title': '', + 'user_id': 14218199, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1366, + 'img_id': 556374988, + 'img_id_str': '556374988', + 'title': '', + 'user_id': 14218199, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1332, + 'img_id': 363240342, + 'img_id_str': '363240342', + 'title': '', + 'user_id': 14218199, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2048, + 'img_id': 48274078, + 'img_id_str': '48274078', + 'title': '', + 'user_id': 14218199, + 'width': 1367 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1366, + 'img_id': 554343895, + 'img_id_str': '554343895', + 'title': '', + 'user_id': 14218199, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1366, + 'img_id': 69770100, + 'img_id_str': '69770100', + 'title': '', + 'user_id': 14218199, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1366, + 'img_id': 481204791, + 'img_id_str': '481204791', + 'title': '', + 'user_id': 14218199, + 'width': 2048 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '9', + 'passed_time': '2019年08月31日', + 'post_id': 51175586, + 'published_at': '2019-08-31 00:00:55', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 10, + 'site': { + 'description': '简简单单的拍~', + 'domain': '', + 'followers': 3807, + 'has_everphoto_note': false, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_14218199_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '人像专业户', + 'site_id': '14218199', + 'type': 'user', + 'url': 'https://tuchong.com/14218199/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '14218199', + 'sites': [], + 'tags': ['人像'], + 'title': '一组室外人像', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/14218199/51175586/', + 'views': 24274 + }, + { + 'author_id': '2391671', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 25, + 'content': '我在秋天收集浪漫,好在初冬赠予你。\n\n摄影/后期:毛茸茸CR2', + 'created_at': '2019-12-11 20:19:44', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '我在秋天收集浪漫,好在初冬赠予你。\n\n摄影/后期:毛茸茸CR2', + 'favorite_list_prefix': [], + 'favorites': 382, + 'image_count': 42, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 90096594, + 'img_id_str': '90096594', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 643351800, + 'img_id_str': '643351800', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 272548954, + 'img_id_str': '272548954', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 592037202, + 'img_id_str': '592037202', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3070, + 'img_id': 547406900, + 'img_id_str': '547406900', + 'title': '001', + 'user_id': 2391671, + 'width': 2057 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 208126656, + 'img_id_str': '208126656', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 85967474, + 'img_id_str': '85967474', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 102351975, + 'img_id_str': '102351975', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 544130446, + 'img_id_str': '544130446', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 372490522, + 'img_id_str': '372490522', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 301581882, + 'img_id_str': '301581882', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 335332300, + 'img_id_str': '335332300', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 42517600, + 'img_id_str': '42517600', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 595247855, + 'img_id_str': '595247855', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 112837392, + 'img_id_str': '112837392', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 450872188, + 'img_id_str': '450872188', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 240632421, + 'img_id_str': '240632421', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 88130319, + 'img_id_str': '88130319', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 531809063, + 'img_id_str': '531809063', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 320848985, + 'img_id_str': '320848985', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 452969315, + 'img_id_str': '452969315', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 43369319, + 'img_id_str': '43369319', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 495174504, + 'img_id_str': '495174504', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 348832001, + 'img_id_str': '348832001', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 350077801, + 'img_id_str': '350077801', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 513655548, + 'img_id_str': '513655548', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 353289546, + 'img_id_str': '353289546', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 198820556, + 'img_id_str': '198820556', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1763, + 'img_id': 230736931, + 'img_id_str': '230736931', + 'title': '001', + 'user_id': 2391671, + 'width': 1181 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 234013466, + 'img_id_str': '234013466', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2044, + 'img_id': 79282804, + 'img_id_str': '79282804', + 'title': '001', + 'user_id': 2391671, + 'width': 1370 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 250266352, + 'img_id_str': '250266352', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 578929518, + 'img_id_str': '578929518', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 261014354, + 'img_id_str': '261014354', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 213304710, + 'img_id_str': '213304710', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 133415679, + 'img_id_str': '133415679', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 113230657, + 'img_id_str': '113230657', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 164283086, + 'img_id_str': '164283086', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3578, + 'img_id': 88588939, + 'img_id_str': '88588939', + 'title': '001', + 'user_id': 2391671, + 'width': 2397 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 219530244, + 'img_id_str': '219530244', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 599769979, + 'img_id_str': '599769979', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2397, + 'img_id': 340312806, + 'img_id_str': '340312806', + 'title': '001', + 'user_id': 2391671, + 'width': 3578 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '23', + 'passed_time': '2019年12月11日', + 'post_id': 60276832, + 'published_at': '2019-12-11 20:19:44', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 17, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'rongrong.tuchong.com', + 'followers': 4905, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2391671_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '毛茸茸CR²', + 'site_id': '2391671', + 'type': 'user', + 'url': 'https://rongrong.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2391671', + 'sites': [], + 'tags': ['人像', '北京', '深圳', '写真', '约拍'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://rongrong.tuchong.com/60276832/', + 'views': 12249 + }, + { + 'author_id': '476401', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 68, + 'content': '', + 'created_at': '2019-12-04 00:14:45', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '上海人像圈子', + '高颜值女神聚集地', + '文珺Ronnie人像拍摄活动', + '我们都爱日系摄影', + '初恋的颜色是粉色', + '分享神仙颜值', + '写真人像摄影', + '糖水人像小组', + '我要上开屏', + '人像摄影集' + ], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 2388, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 617398503, + 'img_id_str': '617398503', + 'title': '001', + 'user_id': 476401, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 532464484, + 'img_id_str': '532464484', + 'title': '001', + 'user_id': 476401, + 'width': 2667 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2667, + 'img_id': 593740168, + 'img_id_str': '593740168', + 'title': '001', + 'user_id': 476401, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2667, + 'img_id': 647873529, + 'img_id_str': '647873529', + 'title': '001', + 'user_id': 476401, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 348963306, + 'img_id_str': '348963306', + 'title': '001', + 'user_id': 476401, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 432063232, + 'img_id_str': '432063232', + 'title': '001', + 'user_id': 476401, + 'width': 2667 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 282444138, + 'img_id_str': '282444138', + 'title': '001', + 'user_id': 476401, + 'width': 2667 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 232767590, + 'img_id_str': '232767590', + 'title': '001', + 'user_id': 476401, + 'width': 2667 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2667, + 'img_id': 463847568, + 'img_id_str': '463847568', + 'title': '001', + 'user_id': 476401, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 116245280, + 'img_id_str': '116245280', + 'title': '001', + 'user_id': 476401, + 'width': 2667 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '53', + 'passed_time': '2019年12月04日', + 'post_id': 59844569, + 'published_at': '2019-12-04 00:14:45', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 67, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'ronnie.tuchong.com', + 'followers': 69340, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_476401_10', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '文珺Ronnie', + 'site_id': '476401', + 'type': 'user', + 'url': 'https://ronnie.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '476401', + 'sites': [], + 'tags': [ + '上海人像圈子', + '高颜值女神聚集地', + '文珺Ronnie人像拍摄活动', + '我们都爱日系摄影', + '初恋的颜色是粉色', + '分享神仙颜值' + ], + 'title': '『九月微醺.少女日记』', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://ronnie.tuchong.com/59844569/', + 'views': 103606 + }, + { + 'author_id': '2598104', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 35, + 'content': '章若楠的颜', + 'created_at': '2019-11-11 15:23:06', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['少女写真圈子'], + 'excerpt': '章若楠的颜', + 'favorite_list_prefix': [], + 'favorites': 849, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 225361092, + 'img_id_str': '225361092', + 'title': '001', + 'user_id': 2598104, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 590461947, + 'img_id_str': '590461947', + 'title': '001', + 'user_id': 2598104, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 588889210, + 'img_id_str': '588889210', + 'title': '001', + 'user_id': 2598104, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 383237244, + 'img_id_str': '383237244', + 'title': '001', + 'user_id': 2598104, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 34519920, + 'img_id_str': '34519920', + 'title': '001', + 'user_id': 2598104, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 363445150, + 'img_id_str': '363445150', + 'title': '001', + 'user_id': 2598104, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 327531751, + 'img_id_str': '327531751', + 'title': '001', + 'user_id': 2598104, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 144555261, + 'img_id_str': '144555261', + 'title': '001', + 'user_id': 2598104, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 425311379, + 'img_id_str': '425311379', + 'title': '001', + 'user_id': 2598104, + 'width': 1080 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '34', + 'passed_time': '2019年11月11日', + 'post_id': 58325283, + 'published_at': '2019-11-11 15:23:06', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 30, + 'site': { + 'description': '知名摄影博主', + 'domain': '', + 'followers': 10365, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2598104_12', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '写真bot', + 'site_id': '2598104', + 'type': 'user', + 'url': 'https://tuchong.com/2598104/', + 'verification_list': [ + {'verification_reason': '知名摄影博主', 'verification_type': 12} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2598104', + 'sites': [], + 'tags': ['少女写真圈子'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2598104/58325283/', + 'views': 30384 + }, + { + 'author_id': '333261', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 65, + 'content': '江头未是风波恶,别有人间行路难!\n出境:@庄翰 @释延戛 @大蛇\n#爱良安摄影教学#\n同行徒弟@急流@八千祟', + 'created_at': '2019-10-28 10:21:13', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '江头未是风波恶,别有人间行路难!\n出境:@庄翰 @释延戛 @大蛇\n#爱良安摄影教学#\n同行徒弟@急流@八千祟', + 'favorite_list_prefix': [], + 'favorites': 1236, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1077, + 'img_id': 343128005, + 'img_id_str': '343128005', + 'title': '', + 'user_id': 333261, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 278378489, + 'img_id_str': '278378489', + 'title': '', + 'user_id': 333261, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3579, + 'img_id': 271038510, + 'img_id_str': '271038510', + 'title': '', + 'user_id': 333261, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3754, + 'img_id': 87537737, + 'img_id_str': '87537737', + 'title': '', + 'user_id': 333261, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2845, + 'img_id': 89896840, + 'img_id_str': '89896840', + 'title': '', + 'user_id': 333261, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 399685385, + 'img_id_str': '399685385', + 'title': '', + 'user_id': 333261, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 424523575, + 'img_id_str': '424523575', + 'title': '', + 'user_id': 333261, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 223852759, + 'img_id_str': '223852759', + 'title': '', + 'user_id': 333261, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1766, + 'img_id': 424589152, + 'img_id_str': '424589152', + 'title': '', + 'user_id': 333261, + 'width': 1280 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '65', + 'passed_time': '2019年10月28日', + 'post_id': 57155152, + 'published_at': '2019-10-28 10:21:13', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 58, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'alasy.tuchong.com', + 'followers': 28007, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_333261_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '爱良安', + 'site_id': '333261', + 'type': 'user', + 'url': 'https://alasy.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '333261', + 'sites': [], + 'tags': ['人像', '人像摄影', '武侠', '古风'], + 'title': '定风波', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://alasy.tuchong.com/57155152/', + 'views': 49753 + }, + { + 'author_id': '3738183', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 63, + 'content': '游官湖村~', + 'created_at': '2019-10-09 22:43:41', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '游官湖村~', + 'favorite_list_prefix': [], + 'favorites': 1762, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2500, + 'img_id': 363966820, + 'img_id_str': '363966820', + 'title': '707352', + 'user_id': 3738183, + 'width': 1666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2500, + 'img_id': 381530265, + 'img_id_str': '381530265', + 'title': '707355', + 'user_id': 3738183, + 'width': 1666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2500, + 'img_id': 423276663, + 'img_id_str': '423276663', + 'title': '707351', + 'user_id': 3738183, + 'width': 1666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2500, + 'img_id': 88649995, + 'img_id_str': '88649995', + 'title': '707357', + 'user_id': 3738183, + 'width': 1666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2500, + 'img_id': 385331529, + 'img_id_str': '385331529', + 'title': '707349', + 'user_id': 3738183, + 'width': 1666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2500, + 'img_id': 561492439, + 'img_id_str': '561492439', + 'title': '707348', + 'user_id': 3738183, + 'width': 1666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2500, + 'img_id': 342208537, + 'img_id_str': '342208537', + 'title': '707350', + 'user_id': 3738183, + 'width': 1666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2500, + 'img_id': 609202187, + 'img_id_str': '609202187', + 'title': '707354', + 'user_id': 3738183, + 'width': 1666 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2500, + 'img_id': 475442967, + 'img_id_str': '475442967', + 'title': '707356', + 'user_id': 3738183, + 'width': 1666 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '62', + 'passed_time': '2019年10月09日', + 'post_id': 55410216, + 'published_at': '2019-10-09 22:43:41', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 61, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 15518, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3738183_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '钟月月', + 'site_id': '3738183', + 'type': 'user', + 'url': 'https://tuchong.com/3738183/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3738183', + 'sites': [], + 'tags': ['旅游', '外拍', '官湖村', '女孩'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3738183/55410216/', + 'views': 61803 + }, + { + 'author_id': '2993890', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 51, + 'content': '黑白灰.', + 'created_at': '2019-10-01 22:29:20', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '黑白灰.', + 'favorite_list_prefix': [], + 'favorites': 1220, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 207137639, + 'img_id_str': '207137639', + 'title': '001', + 'user_id': 2993890, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1787, + 'img_id': 192588567, + 'img_id_str': '192588567', + 'title': '001', + 'user_id': 2993890, + 'width': 1191 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 264023083, + 'img_id_str': '264023083', + 'title': '001', + 'user_id': 2993890, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 208186020, + 'img_id_str': '208186020', + 'title': '001', + 'user_id': 2993890, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 475310528, + 'img_id_str': '475310528', + 'title': '001', + 'user_id': 2993890, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 400141439, + 'img_id_str': '400141439', + 'title': '001', + 'user_id': 2993890, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 178891672, + 'img_id_str': '178891672', + 'title': '001', + 'user_id': 2993890, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 368159626, + 'img_id_str': '368159626', + 'title': '001', + 'user_id': 2993890, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1792, + 'img_id': 570076065, + 'img_id_str': '570076065', + 'title': '001', + 'user_id': 2993890, + 'width': 1280 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '46', + 'passed_time': '2019年10月01日', + 'post_id': 54204555, + 'published_at': '2019-10-01 22:29:20', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 50, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 23392, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2993890_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '没有喵的尾巴巴', + 'site_id': '2993890', + 'type': 'user', + 'url': 'https://tuchong.com/2993890/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2993890', + 'sites': [], + 'tags': ['黑白'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2993890/54204555/', + 'views': 56522 + }, + { + 'author_id': '1468214', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 53, + 'content': '《昆曲·醉妃》', + 'created_at': '2019-09-30 00:53:06', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '《昆曲·醉妃》', + 'favorite_list_prefix': [], + 'favorites': 876, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 46311910, + 'img_id_str': '46311910', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 139831883, + 'img_id_str': '139831883', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 563784375, + 'img_id_str': '563784375', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4340, + 'img_id': 515090636, + 'img_id_str': '515090636', + 'title': '001', + 'user_id': 1468214, + 'width': 6510 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 166963522, + 'img_id_str': '166963522', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6620, + 'img_id': 326019950, + 'img_id_str': '326019950', + 'title': '001', + 'user_id': 1468214, + 'width': 4412 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 285125343, + 'img_id_str': '285125343', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4390, + 'img_id': 598583757, + 'img_id_str': '598583757', + 'title': '001', + 'user_id': 1468214, + 'width': 6586 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4216, + 'img_id': 613132925, + 'img_id_str': '613132925', + 'title': '001', + 'user_id': 1468214, + 'width': 6324 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 410691931, + 'img_id_str': '410691931', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 238004493, + 'img_id_str': '238004493', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6642, + 'img_id': 321038910, + 'img_id_str': '321038910', + 'title': '001', + 'user_id': 1468214, + 'width': 4428 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '48', + 'passed_time': '2019年09月30日', + 'post_id': 53975622, + 'published_at': '2019-09-30 00:53:06', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 22, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 71180, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1468214_7', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'GK__', + 'site_id': '1468214', + 'type': 'user', + 'url': 'https://tuchong.com/1468214/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1468214', + 'sites': [], + 'tags': ['人像', '戏剧'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1468214/53975622/', + 'views': 53623 + }, + { + 'author_id': '1075170', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 106, + 'content': '', + 'created_at': '2019-08-29 23:48:30', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['高颜值女神聚集地'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 2106, + 'image_count': 15, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4912, + 'img_id': 85891769, + 'img_id_str': '85891769', + 'title': '', + 'user_id': 1075170, + 'width': 7360 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 565353501, + 'img_id_str': '565353501', + 'title': '', + 'user_id': 1075170, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 220110318, + 'img_id_str': '220110318', + 'title': '', + 'user_id': 1075170, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 280009496, + 'img_id_str': '280009496', + 'title': '', + 'user_id': 1075170, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 396532527, + 'img_id_str': '396532527', + 'title': '', + 'user_id': 1075170, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 452369073, + 'img_id_str': '452369073', + 'title': '', + 'user_id': 1075170, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7360, + 'img_id': 335977624, + 'img_id_str': '335977624', + 'title': '', + 'user_id': 1075170, + 'width': 4912 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 375036841, + 'img_id_str': '375036841', + 'title': '', + 'user_id': 1075170, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 381196677, + 'img_id_str': '381196677', + 'title': '', + 'user_id': 1075170, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 237673402, + 'img_id_str': '237673402', + 'title': '', + 'user_id': 1075170, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 249404243, + 'img_id_str': '249404243', + 'title': '', + 'user_id': 1075170, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 69246353, + 'img_id_str': '69246353', + 'title': '', + 'user_id': 1075170, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 98802148, + 'img_id_str': '98802148', + 'title': '', + 'user_id': 1075170, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4912, + 'img_id': 603167775, + 'img_id_str': '603167775', + 'title': '', + 'user_id': 1075170, + 'width': 7360 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 307337861, + 'img_id_str': '307337861', + 'title': '', + 'user_id': 1075170, + 'width': 5304 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '92', + 'passed_time': '2019年08月29日', + 'post_id': 51071348, + 'published_at': '2019-08-29 23:48:30', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 128, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'xuexianyu.tuchong.com', + 'followers': 18506, + 'has_everphoto_note': false, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1075170_6', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '薛咸鱼', + 'site_id': '1075170', + 'type': 'user', + 'url': 'https://xuexianyu.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1075170', + 'sites': [], + 'tags': ['高颜值女神聚集地', '人像', '色彩', '美女', '女孩', '日系'], + 'title': '武姬', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://xuexianyu.tuchong.com/51071348/', + 'views': 125023 + }, + { + 'author_id': '3738183', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 153, + 'content': '高冷的女孩也有可爱的一面ớ ₃ờ', + 'created_at': '2019-08-08 23:19:02', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['我要上“首页推荐位”'], + 'excerpt': '高冷的女孩也有可爱的一面ớ ₃ờ', + 'favorite_list_prefix': [], + 'favorites': 4366, + 'image_count': 8, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 71273442, + 'img_id_str': '71273442', + 'title': '476526', + 'user_id': 3738183, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 377850543, + 'img_id_str': '377850543', + 'title': '476528', + 'user_id': 3738183, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 43354680, + 'img_id_str': '43354680', + 'title': '476525', + 'user_id': 3738183, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 85559809, + 'img_id_str': '85559809', + 'title': '476521', + 'user_id': 3738183, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 104696138, + 'img_id_str': '104696138', + 'title': '476527', + 'user_id': 3738183, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 254773945, + 'img_id_str': '254773945', + 'title': '476529', + 'user_id': 3738183, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 123243073, + 'img_id_str': '123243073', + 'title': '476523', + 'user_id': 3738183, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 597133923, + 'img_id_str': '597133923', + 'title': '476522', + 'user_id': 3738183, + 'width': 5792 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '151', + 'passed_time': '2019年08月08日', + 'post_id': 48313503, + 'published_at': '2019-08-08 23:19:02', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 118, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 15518, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3738183_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '钟月月', + 'site_id': '3738183', + 'type': 'user', + 'url': 'https://tuchong.com/3738183/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3738183', + 'sites': [], + 'tags': ['我要上“首页推荐位”', '可爱', '漂亮的', '时尚'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3738183/48313503/', + 'views': 150492 + }, + { + 'author_id': '381497', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 163, + 'content': '小紫', + 'created_at': '2019-11-19 17:33:28', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '小紫', + 'favorite_list_prefix': [], + 'favorites': 1947, + 'image_count': 24, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 114802345, + 'img_id_str': '114802345', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 593149404, + 'img_id_str': '593149404', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 516865941, + 'img_id_str': '516865941', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 44285325, + 'img_id_str': '44285325', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 576110215, + 'img_id_str': '576110215', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 501267742, + 'img_id_str': '501267742', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 351584375, + 'img_id_str': '351584375', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 451526347, + 'img_id_str': '451526347', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 601538229, + 'img_id_str': '601538229', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 68534055, + 'img_id_str': '68534055', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 275038022, + 'img_id_str': '275038022', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 243973718, + 'img_id_str': '243973718', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 206815409, + 'img_id_str': '206815409', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 486260166, + 'img_id_str': '486260166', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 398966769, + 'img_id_str': '398966769', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 586071364, + 'img_id_str': '586071364', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 138657116, + 'img_id_str': '138657116', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 263241389, + 'img_id_str': '263241389', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 171163012, + 'img_id_str': '171163012', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 520928758, + 'img_id_str': '520928758', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 587186131, + 'img_id_str': '587186131', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 498319803, + 'img_id_str': '498319803', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 489930317, + 'img_id_str': '489930317', + 'title': '', + 'user_id': 381497, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1799, + 'img_id': 611630647, + 'img_id_str': '611630647', + 'title': '', + 'user_id': 381497, + 'width': 1200 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '140', + 'passed_time': '2019年11月19日', + 'post_id': 58888289, + 'published_at': '2019-11-19 17:33:28', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 109, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 38516, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_381497_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '老威威', + 'site_id': '381497', + 'type': 'user', + 'url': 'https://tuchong.com/381497/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '381497', + 'sites': [], + 'tags': ['人像', '美女', '小清新'], + 'title': '有酒靥的女孩', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/381497/58888289/', + 'views': 118958 + }, + { + 'author_id': '3568820', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 69, + 'content': '', + 'created_at': '2019-11-14 14:48:05', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 993, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 403291512, + 'img_id_str': '403291512', + 'title': '', + 'user_id': 3568820, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 442416633, + 'img_id_str': '442416633', + 'title': '', + 'user_id': 3568820, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 605797647, + 'img_id_str': '605797647', + 'title': '', + 'user_id': 3568820, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 250133821, + 'img_id_str': '250133821', + 'title': '', + 'user_id': 3568820, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 408140843, + 'img_id_str': '408140843', + 'title': '', + 'user_id': 3568820, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5184, + 'img_id': 90750426, + 'img_id_str': '90750426', + 'title': '', + 'user_id': 3568820, + 'width': 3456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5254, + 'img_id': 507231542, + 'img_id_str': '507231542', + 'title': '', + 'user_id': 3568820, + 'width': 3502 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5254, + 'img_id': 374979805, + 'img_id_str': '374979805', + 'title': '', + 'user_id': 3568820, + 'width': 3502 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5646, + 'img_id': 180141656, + 'img_id_str': '180141656', + 'title': '', + 'user_id': 3568820, + 'width': 4232 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '67', + 'passed_time': '2019年11月14日', + 'post_id': 58524195, + 'published_at': '2019-11-14 14:48:05', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 67, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 2473, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3568820_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '一木各照相馆', + 'site_id': '3568820', + 'type': 'user', + 'url': 'https://tuchong.com/3568820/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3568820', + 'sites': [], + 'tags': ['人像', '少女写真', '写真', '一木各', '网红照相馆'], + 'title': 'ANGEL·艺术形象照(一)', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3568820/58524195/', + 'views': 29628 + }, + { + 'author_id': '1642567', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 75, + 'content': '一种东方美的力量', + 'created_at': '2019-10-22 11:02:14', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '一种东方美的力量', + 'favorite_list_prefix': [], + 'favorites': 959, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2088, + 'img_id': 485799501, + 'img_id_str': '485799501', + 'title': '001', + 'user_id': 1642567, + 'width': 1174 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 259699734, + 'img_id_str': '259699734', + 'title': '001', + 'user_id': 1642567, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1921, + 'img_id': 178435493, + 'img_id_str': '178435493', + 'title': '001', + 'user_id': 1642567, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1921, + 'img_id': 516273362, + 'img_id_str': '516273362', + 'title': '001', + 'user_id': 1642567, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1672, + 'img_id': 654882471, + 'img_id_str': '654882471', + 'title': '001', + 'user_id': 1642567, + 'width': 940 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1918, + 'img_id': 248428339, + 'img_id_str': '248428339', + 'title': '001', + 'user_id': 1642567, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1921, + 'img_id': 591574016, + 'img_id_str': '591574016', + 'title': '001', + 'user_id': 1642567, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3831, + 'img_id': 163034275, + 'img_id_str': '163034275', + 'title': '001', + 'user_id': 1642567, + 'width': 2155 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1420, + 'img_id': 58832513, + 'img_id_str': '58832513', + 'title': '001', + 'user_id': 1642567, + 'width': 798 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 404862588, + 'img_id_str': '404862588', + 'title': '001', + 'user_id': 1642567, + 'width': 1080 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '73', + 'passed_time': '2019年10月22日', + 'post_id': 56626071, + 'published_at': '2019-10-22 11:02:14', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 68, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 6844, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1642567_5', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '子夜鸟', + 'site_id': '1642567', + 'type': 'user', + 'url': 'https://tuchong.com/1642567/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1642567', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1642567/56626071/', + 'views': 21149 + }, + { + 'author_id': '1408220', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 53, + 'content': '午夜公园', + 'created_at': '2019-10-18 07:22:37', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '午夜公园', + 'favorite_list_prefix': [], + 'favorites': 711, + 'image_count': 21, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 597799730, + 'img_id_str': '597799730', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 326546636, + 'img_id_str': '326546636', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 617919509, + 'img_id_str': '617919509', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 512144390, + 'img_id_str': '512144390', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 126989202, + 'img_id_str': '126989202', + 'title': '001', + 'user_id': 1408220, + 'width': 3240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 596554994, + 'img_id_str': '596554994', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 458667289, + 'img_id_str': '458667289', + 'title': '001', + 'user_id': 1408220, + 'width': 3240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3377, + 'img_id': 261797035, + 'img_id_str': '261797035', + 'title': '001', + 'user_id': 1408220, + 'width': 750 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 455717549, + 'img_id_str': '455717549', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 438678131, + 'img_id_str': '438678131', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 419345070, + 'img_id_str': '419345070', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 521254011, + 'img_id_str': '521254011', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 261076479, + 'img_id_str': '261076479', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 64271095, + 'img_id_str': '64271095', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2160, + 'img_id': 68924571, + 'img_id_str': '68924571', + 'title': '001', + 'user_id': 1408220, + 'width': 3240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 107459650, + 'img_id_str': '107459650', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 652718978, + 'img_id_str': '652718978', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 248755272, + 'img_id_str': '248755272', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3377, + 'img_id': 387756859, + 'img_id_str': '387756859', + 'title': '001', + 'user_id': 1408220, + 'width': 750 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 389854393, + 'img_id_str': '389854393', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3240, + 'img_id': 179286621, + 'img_id_str': '179286621', + 'title': '001', + 'user_id': 1408220, + 'width': 2160 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '27', + 'passed_time': '2019年10月18日', + 'post_id': 56196163, + 'published_at': '2019-10-18 07:22:37', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '11', + 'rqt_id': '', + 'shares': 19, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 16047, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1408220_15', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '雨罙_', + 'site_id': '1408220', + 'type': 'user', + 'url': 'https://tuchong.com/1408220/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1408220', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1408220/56196163/', + 'views': 44265 + }, + { + 'author_id': '1781920', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 47, + 'content': '天气渐渐凉,才会珍惜阳光的温度。\n\n大熊作品X谨谨 ‖ #写真#', + 'created_at': '2019-09-22 08:38:53', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['武汉日系摄影师联盟', '人像摄影集'], + 'excerpt': '天气渐渐凉,才会珍惜阳光的温度。\n\n大熊作品X谨谨 ‖ #写真#', + 'favorite_list_prefix': [], + 'favorites': 363, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4002, + 'img_id': 174958586, + 'img_id_str': '174958586', + 'title': '1586973', + 'user_id': 1781920, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 39495390, + 'img_id_str': '39495390', + 'title': '1586987', + 'user_id': 1781920, + 'width': 2668 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4002, + 'img_id': 453355607, + 'img_id_str': '453355607', + 'title': '1586971', + 'user_id': 1781920, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 360097445, + 'img_id_str': '360097445', + 'title': '1586969', + 'user_id': 1781920, + 'width': 4002 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4002, + 'img_id': 442410719, + 'img_id_str': '442410719', + 'title': '1586970', + 'user_id': 1781920, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 227256089, + 'img_id_str': '227256089', + 'title': '1586976', + 'user_id': 1781920, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4002, + 'img_id': 648193599, + 'img_id_str': '648193599', + 'title': '1586975', + 'user_id': 1781920, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 593995843, + 'img_id_str': '593995843', + 'title': '1586977', + 'user_id': 1781920, + 'width': 4002 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 321365080, + 'img_id_str': '321365080', + 'title': '1586988', + 'user_id': 1781920, + 'width': 4002 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4002, + 'img_id': 199010072, + 'img_id_str': '199010072', + 'title': '1586978', + 'user_id': 1781920, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 332899481, + 'img_id_str': '332899481', + 'title': '1586979', + 'user_id': 1781920, + 'width': 4002 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '20', + 'passed_time': '2019年09月22日', + 'post_id': 53241074, + 'published_at': '2019-09-22 08:38:53', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 13, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 3650, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1781920_4', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '背相机的大熊', + 'site_id': '1781920', + 'type': 'user', + 'url': 'https://tuchong.com/1781920/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1781920', + 'sites': [], + 'tags': ['武汉日系摄影师联盟', '人像摄影集', '冷色调', '情绪', '写真', '日系'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1781920/53241074/', + 'views': 15387 + }, + { + 'author_id': '2716090', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 99, + 'content': '一个人的婚纱', + 'created_at': '2019-09-13 10:40:43', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '一个人的婚纱', + 'favorite_list_prefix': [], + 'favorites': 1218, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 107258791, + 'img_id_str': '107258791', + 'title': '3061318', + 'user_id': 2716090, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 625844689, + 'img_id_str': '625844689', + 'title': '3061317', + 'user_id': 2716090, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 478323262, + 'img_id_str': '478323262', + 'title': '3061316', + 'user_id': 2716090, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 342598062, + 'img_id_str': '342598062', + 'title': '3061315', + 'user_id': 2716090, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 144417435, + 'img_id_str': '144417435', + 'title': '3061314', + 'user_id': 2716090, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1000, + 'img_id': 315597884, + 'img_id_str': '315597884', + 'title': '3061313', + 'user_id': 2716090, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 339649034, + 'img_id_str': '339649034', + 'title': '3061312', + 'user_id': 2716090, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 197304994, + 'img_id_str': '197304994', + 'title': '3061309', + 'user_id': 2716090, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 77505154, + 'img_id_str': '77505154', + 'title': '3061310', + 'user_id': 2716090, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1000, + 'img_id': 445292914, + 'img_id_str': '445292914', + 'title': '3061308', + 'user_id': 2716090, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 244425389, + 'img_id_str': '244425389', + 'title': '3061311', + 'user_id': 2716090, + 'width': 1000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '96', + 'passed_time': '2019年09月13日', + 'post_id': 52425210, + 'published_at': '2019-09-13 10:40:43', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 93, + 'site': { + 'description': '深圳自由摄影师', + 'domain': '', + 'followers': 5223, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2716090_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '蜂子', + 'site_id': '2716090', + 'type': 'user', + 'url': 'https://tuchong.com/2716090/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '2716090', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2716090/52425210/', + 'views': 88311 + }, + { + 'author_id': '11470935', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 32, + 'content': '', + 'created_at': '2019-09-10 20:44:41', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 698, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 998, + 'img_id': 111780128, + 'img_id_str': '111780128', + 'title': '001', + 'user_id': 11470935, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1010, + 'img_id': 274374988, + 'img_id_str': '274374988', + 'title': '001', + 'user_id': 11470935, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1010, + 'img_id': 527802415, + 'img_id_str': '527802415', + 'title': '001', + 'user_id': 11470935, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1010, + 'img_id': 443261380, + 'img_id_str': '443261380', + 'title': '001', + 'user_id': 11470935, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1010, + 'img_id': 143565083, + 'img_id_str': '143565083', + 'title': '001', + 'user_id': 11470935, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1010, + 'img_id': 372023714, + 'img_id_str': '372023714', + 'title': '001', + 'user_id': 11470935, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1010, + 'img_id': 207593546, + 'img_id_str': '207593546', + 'title': '001', + 'user_id': 11470935, + 'width': 1500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 48865752, + 'img_id_str': '48865752', + 'title': '001', + 'user_id': 11470935, + 'width': 1010 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 230728178, + 'img_id_str': '230728178', + 'title': '001', + 'user_id': 11470935, + 'width': 1010 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1010, + 'img_id': 369336620, + 'img_id_str': '369336620', + 'title': '001', + 'user_id': 11470935, + 'width': 1500 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '28', + 'passed_time': '2019年09月10日', + 'post_id': 52208563, + 'published_at': '2019-09-10 20:44:41', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 23, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 4470, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_11470935_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Saebyeok-choi', + 'site_id': '11470935', + 'type': 'user', + 'url': 'https://tuchong.com/11470935/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '11470935', + 'sites': [], + 'tags': ['胶片', '戀人', '愛情', '電影'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/11470935/52208563/', + 'views': 73191 + }, + { + 'author_id': '5411691', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 69, + 'content': '烂漫之时', + 'created_at': '2019-07-31 22:10:20', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['分享神仙颜值'], + 'excerpt': '烂漫之时', + 'favorite_list_prefix': [], + 'favorites': 1202, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6124, + 'img_id': 217940567, + 'img_id_str': '217940567', + 'title': '001', + 'user_id': 5411691, + 'width': 4082 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4082, + 'img_id': 284983931, + 'img_id_str': '284983931', + 'title': '001', + 'user_id': 5411691, + 'width': 6124 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6040, + 'img_id': 204178018, + 'img_id_str': '204178018', + 'title': '001', + 'user_id': 5411691, + 'width': 4080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4082, + 'img_id': 383878062, + 'img_id_str': '383878062', + 'title': '001', + 'user_id': 5411691, + 'width': 6124 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1883, + 'img_id': 612205273, + 'img_id_str': '612205273', + 'title': '001', + 'user_id': 5411691, + 'width': 1368 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6124, + 'img_id': 82740387, + 'img_id_str': '82740387', + 'title': '001', + 'user_id': 5411691, + 'width': 4082 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4032, + 'img_id': 150700668, + 'img_id_str': '150700668', + 'title': '001', + 'user_id': 5411691, + 'width': 2688 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4082, + 'img_id': 51020544, + 'img_id_str': '51020544', + 'title': '001', + 'user_id': 5411691, + 'width': 6124 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6124, + 'img_id': 375227216, + 'img_id_str': '375227216', + 'title': '001', + 'user_id': 5411691, + 'width': 4082 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4082, + 'img_id': 395608856, + 'img_id_str': '395608856', + 'title': '001', + 'user_id': 5411691, + 'width': 6124 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '65', + 'passed_time': '2019年07月31日', + 'post_id': 47114221, + 'published_at': '2019-07-31 22:10:20', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 64, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 5825, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_5411691_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '还有一粒', + 'site_id': '5411691', + 'type': 'user', + 'url': 'https://tuchong.com/5411691/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '5411691', + 'sites': [], + 'tags': ['分享神仙颜值', '人像', '花', '柔光'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/5411691/47114221/', + 'views': 49296 + }, + { + 'author_id': '2600543', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 13, + 'content': '出镜:@五更百鬼', + 'created_at': '2019-07-30 10:46:02', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['分享神仙颜值', 'Cosplay摄影集中地'], + 'excerpt': '出镜:@五更百鬼', + 'favorite_list_prefix': [], + 'favorites': 472, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4277, + 'img_id': 212042573, + 'img_id_str': '212042573', + 'title': '', + 'user_id': 2600543, + 'width': 6415 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6577, + 'img_id': 251167225, + 'img_id_str': '251167225', + 'title': '', + 'user_id': 2600543, + 'width': 4385 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6527, + 'img_id': 40665737, + 'img_id_str': '40665737', + 'title': '', + 'user_id': 2600543, + 'width': 4351 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 171343802, + 'img_id_str': '171343802', + 'title': '', + 'user_id': 2600543, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6558, + 'img_id': 267485880, + 'img_id_str': '267485880', + 'title': '', + 'user_id': 2600543, + 'width': 4372 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6415, + 'img_id': 360350217, + 'img_id_str': '360350217', + 'title': '', + 'user_id': 2600543, + 'width': 4277 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6216, + 'img_id': 356221510, + 'img_id_str': '356221510', + 'title': '', + 'user_id': 2600543, + 'width': 4144 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6125, + 'img_id': 220693109, + 'img_id_str': '220693109', + 'title': '', + 'user_id': 2600543, + 'width': 4083 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4376, + 'img_id': 580288663, + 'img_id_str': '580288663', + 'title': '', + 'user_id': 2600543, + 'width': 6564 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '8', + 'passed_time': '2019年07月30日', + 'post_id': 46876721, + 'published_at': '2019-07-30 10:46:02', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 5, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': 'sanyue015.tuchong.com', + 'followers': 13819, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2600543_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '叁月life', + 'site_id': '2600543', + 'type': 'user', + 'url': 'https://sanyue015.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2600543', + 'sites': [], + 'tags': ['分享神仙颜值', 'Cosplay摄影集中地', '人像', '摄影', 'cos', 'Cosplay'], + 'title': '#加藤惠 #兔女郎 #cos #深圳约拍', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://sanyue015.tuchong.com/46876721/', + 'views': 21885 + }, + { + 'author_id': '406880', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 114, + 'content': '', + 'created_at': '2019-07-24 06:45:21', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1730, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 317877409, + 'img_id_str': '317877409', + 'title': '001', + 'user_id': 406880, + 'width': 958 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 104990288, + 'img_id_str': '104990288', + 'title': '001', + 'user_id': 406880, + 'width': 958 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '73', + 'passed_time': '2019年07月24日', + 'post_id': 45955842, + 'published_at': '2019-07-24 06:45:21', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 49, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 24454, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_406880_9', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '风-vision', + 'site_id': '406880', + 'type': 'user', + 'url': 'https://tuchong.com/406880/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '406880', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/406880/45955842/', + 'views': 145954 + }, + { + 'author_id': '4667676', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 4, + 'content': '新街口夜色!', + 'created_at': '2019-12-28 09:07:08', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['江苏摄影俱乐部'], + 'excerpt': '新街口夜色!', + 'favorite_list_prefix': [], + 'favorites': 39, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 439141644, + 'img_id_str': '439141644', + 'title': '1245115', + 'user_id': 4667676, + 'width': 2000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '2019年12月28日', + 'post_id': 61079094, + 'published_at': '2019-12-28 09:07:08', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '视觉中国、图虫签约摄影师!\n中国摄影著作权协会、南京市摄影家协会会员', + 'domain': '', + 'followers': 583, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_4667676_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '左岸的风', + 'site_id': '4667676', + 'type': 'user', + 'url': 'https://tuchong.com/4667676/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '4667676', + 'sites': [], + 'tags': ['江苏摄影俱乐部', '薄暮', '高楼', '建筑', '城市', '市中心'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/4667676/61079094/', + 'views': 1747 + }, + { + 'author_id': '1468214', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 71, + 'content': '《十八岁的单车》', + 'created_at': '2019-09-11 00:15:59', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '《十八岁的单车》', + 'favorite_list_prefix': [], + 'favorites': 1718, + 'image_count': 30, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4537, + 'img_id': 563388801, + 'img_id_str': '563388801', + 'title': '001', + 'user_id': 1468214, + 'width': 4538 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 63676975, + 'img_id_str': '63676975', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4588, + 'img_id': 563978504, + 'img_id_str': '563978504', + 'title': '001', + 'user_id': 1468214, + 'width': 4590 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 334996197, + 'img_id_str': '334996197', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4584, + 'img_id': 44474419, + 'img_id_str': '44474419', + 'title': '001', + 'user_id': 1468214, + 'width': 4585 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4564, + 'img_id': 534225459, + 'img_id_str': '534225459', + 'title': '001', + 'user_id': 1468214, + 'width': 4565 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 196190370, + 'img_id_str': '196190370', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 37658965, + 'img_id_str': '37658965', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 427336366, + 'img_id_str': '427336366', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4552, + 'img_id': 301506947, + 'img_id_str': '301506947', + 'title': '001', + 'user_id': 1468214, + 'width': 4554 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 50962618, + 'img_id_str': '50962618', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4624, + 'img_id': 105882422, + 'img_id_str': '105882422', + 'title': '001', + 'user_id': 1468214, + 'width': 4626 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 239903878, + 'img_id_str': '239903878', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 499818541, + 'img_id_str': '499818541', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 450601300, + 'img_id_str': '450601300', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 67805658, + 'img_id_str': '67805658', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 266314241, + 'img_id_str': '266314241', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4476, + 'img_id': 185835747, + 'img_id_str': '185835747', + 'title': '001', + 'user_id': 1468214, + 'width': 4475 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 109355570, + 'img_id_str': '109355570', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 526688956, + 'img_id_str': '526688956', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 51683879, + 'img_id_str': '51683879', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 577217052, + 'img_id_str': '577217052', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 489267438, + 'img_id_str': '489267438', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 117940911, + 'img_id_str': '117940911', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 532194183, + 'img_id_str': '532194183', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 416588053, + 'img_id_str': '416588053', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 171614954, + 'img_id_str': '171614954', + 'title': '001', + 'user_id': 1468214, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4571, + 'img_id': 185966749, + 'img_id_str': '185966749', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4612, + 'img_id': 102015095, + 'img_id_str': '102015095', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4563, + 'img_id': 78094487, + 'img_id_str': '78094487', + 'title': '001', + 'user_id': 1468214, + 'width': 6720 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '65', + 'passed_time': '2019年09月11日', + 'post_id': 52232122, + 'published_at': '2019-09-11 00:15:59', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 97, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 71180, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1468214_7', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'GK__', + 'site_id': '1468214', + 'type': 'user', + 'url': 'https://tuchong.com/1468214/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1468214', + 'sites': [], + 'tags': ['人像', '骑自行车的人', '女人'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1468214/52232122/', + 'views': 72855 + }, + { + 'author_id': '1303010', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 27, + 'content': '', + 'created_at': '2019-10-28 18:14:16', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['写真人像摄影', '高颜值女神聚集地'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 492, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 295352321, + 'img_id_str': '295352321', + 'title': '635818', + 'user_id': 1303010, + 'width': 1067 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1067, + 'img_id': 542095162, + 'img_id_str': '542095162', + 'title': '635819', + 'user_id': 1303010, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 401324002, + 'img_id_str': '401324002', + 'title': '635820', + 'user_id': 1303010, + 'width': 1067 + }, + { + 'description': '', + 'excerpt': '', + 'height': 707, + 'img_id': 309770334, + 'img_id_str': '309770334', + 'title': '635834', + 'user_id': 1303010, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1080, + 'img_id': 212842485, + 'img_id_str': '212842485', + 'title': '635832', + 'user_id': 1303010, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1067, + 'img_id': 321959965, + 'img_id_str': '321959965', + 'title': '635833', + 'user_id': 1303010, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1600, + 'img_id': 545175314, + 'img_id_str': '545175314', + 'title': '635831', + 'user_id': 1303010, + 'width': 1067 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1067, + 'img_id': 651344158, + 'img_id_str': '651344158', + 'title': '635830', + 'user_id': 1303010, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1067, + 'img_id': 542292166, + 'img_id_str': '542292166', + 'title': '635829', + 'user_id': 1303010, + 'width': 1600 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '25', + 'passed_time': '2019年10月28日', + 'post_id': 57186804, + 'published_at': '2019-10-28 18:14:16', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 17, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': 'devilkman.tuchong.com', + 'followers': 4653, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1303010_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '咕哒囧子', + 'site_id': '1303010', + 'type': 'user', + 'url': 'https://devilkman.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1303010', + 'sites': [], + 'tags': ['写真人像摄影', '高颜值女神聚集地', '水', '人像', 'lolita', '索尼'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://devilkman.tuchong.com/57186804/', + 'views': 13780 + }, + { + 'author_id': '5489136', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 36, + 'content': '“自由游曳在星光下的海上”', + 'created_at': '2019-11-10 23:16:37', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '写真人像摄影', + '江苏摄影俱乐部', + '糖水人像小组', + '暖色调人像', + '我要上开屏', + '分享神仙颜值', + '高颜值女神聚集地' + ], + 'excerpt': '“自由游曳在星光下的海上”', + 'favorite_list_prefix': [], + 'favorites': 647, + 'image_count': 14, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6764, + 'img_id': 47628604, + 'img_id_str': '47628604', + 'title': '001', + 'user_id': 5489136, + 'width': 4992 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4513, + 'img_id': 586136849, + 'img_id_str': '586136849', + 'title': '001', + 'user_id': 5489136, + 'width': 7418 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6991, + 'img_id': 409844753, + 'img_id_str': '409844753', + 'title': '001', + 'user_id': 5489136, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 609073511, + 'img_id_str': '609073511', + 'title': '001', + 'user_id': 5489136, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7424, + 'img_id': 87802047, + 'img_id_str': '87802047', + 'title': '001', + 'user_id': 5489136, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3954, + 'img_id': 438091150, + 'img_id_str': '438091150', + 'title': '001', + 'user_id': 5489136, + 'width': 7030 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5013, + 'img_id': 296795538, + 'img_id_str': '296795538', + 'title': '001', + 'user_id': 5489136, + 'width': 6153 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 342211570, + 'img_id_str': '342211570', + 'title': '001', + 'user_id': 5489136, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6194, + 'img_id': 484424819, + 'img_id_str': '484424819', + 'title': '001', + 'user_id': 5489136, + 'width': 4609 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4918, + 'img_id': 136821777, + 'img_id_str': '136821777', + 'title': '001', + 'user_id': 5489136, + 'width': 7573 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 56212746, + 'img_id_str': '56212746', + 'title': '001', + 'user_id': 5489136, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4440, + 'img_id': 199801646, + 'img_id_str': '199801646', + 'title': '001', + 'user_id': 5489136, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 293583843, + 'img_id_str': '293583843', + 'title': '001', + 'user_id': 5489136, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6785, + 'img_id': 224049935, + 'img_id_str': '224049935', + 'title': '001', + 'user_id': 5489136, + 'width': 5304 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '23', + 'passed_time': '2019年11月10日', + 'post_id': 58293836, + 'published_at': '2019-11-10 23:16:37', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 22, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 5800, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_5489136_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '挽歌大魔王', + 'site_id': '5489136', + 'type': 'user', + 'url': 'https://tuchong.com/5489136/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '5489136', + 'sites': [], + 'tags': ['写真人像摄影', '江苏摄影俱乐部', '糖水人像小组', '暖色调人像', '我要上开屏', '分享神仙颜值'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/5489136/58293836/', + 'views': 22539 + }, + { + 'author_id': '406880', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 26, + 'content': '古城游', + 'created_at': '2019-11-30 05:35:02', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '古城游', + 'favorite_list_prefix': [], + 'favorites': 406, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 58114606, + 'img_id_str': '58114606', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 319275371, + 'img_id_str': '319275371', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 90423394, + 'img_id_str': '90423394', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 442483726, + 'img_id_str': '442483726', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 220774156, + 'img_id_str': '220774156', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 374194676, + 'img_id_str': '374194676', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 434487868, + 'img_id_str': '434487868', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 545177620, + 'img_id_str': '545177620', + 'title': '001', + 'user_id': 406880, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 181125726, + 'img_id_str': '181125726', + 'title': '001', + 'user_id': 406880, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 77709345, + 'img_id_str': '77709345', + 'title': '001', + 'user_id': 406880, + 'width': 1440 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '25', + 'passed_time': '2019年11月30日', + 'post_id': 59596709, + 'published_at': '2019-11-30 05:35:02', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 7, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 24454, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_406880_9', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '风-vision', + 'site_id': '406880', + 'type': 'user', + 'url': 'https://tuchong.com/406880/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '406880', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/406880/59596709/', + 'views': 12133 + }, + { + 'author_id': '2747704', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 32, + 'content': '模特:湘湘\n地点:华南农业大学', + 'created_at': '2019-11-09 13:38:17', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['图虫·OPENSEE2019'], + 'excerpt': '模特:湘湘\n地点:华南农业大学', + 'favorite_list_prefix': [], + 'favorites': 450, + 'image_count': 17, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4016, + 'img_id': 275627044, + 'img_id_str': '275627044', + 'title': '', + 'user_id': 2747704, + 'width': 6016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4016, + 'img_id': 122206942, + 'img_id_str': '122206942', + 'title': '', + 'user_id': 2747704, + 'width': 6016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 305380485, + 'img_id_str': '305380485', + 'title': '', + 'user_id': 2747704, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 404011956, + 'img_id_str': '404011956', + 'title': '', + 'user_id': 2747704, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 125156663, + 'img_id_str': '125156663', + 'title': '', + 'user_id': 2747704, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4024, + 'img_id': 380681496, + 'img_id_str': '380681496', + 'title': '', + 'user_id': 2747704, + 'width': 6048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6048, + 'img_id': 41270226, + 'img_id_str': '41270226', + 'title': '', + 'user_id': 2747704, + 'width': 4024 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6048, + 'img_id': 93305821, + 'img_id_str': '93305821', + 'title': '', + 'user_id': 2747704, + 'width': 4024 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5903, + 'img_id': 528136992, + 'img_id_str': '528136992', + 'title': '', + 'user_id': 2747704, + 'width': 3916 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 368950282, + 'img_id_str': '368950282', + 'title': '', + 'user_id': 2747704, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4016, + 'img_id': 271301850, + 'img_id_str': '271301850', + 'title': '', + 'user_id': 2747704, + 'width': 6016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4016, + 'img_id': 309115934, + 'img_id_str': '309115934', + 'title': '', + 'user_id': 2747704, + 'width': 6016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4016, + 'img_id': 484948935, + 'img_id_str': '484948935', + 'title': '', + 'user_id': 2747704, + 'width': 6016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4016, + 'img_id': 506444846, + 'img_id_str': '506444846', + 'title': '', + 'user_id': 2747704, + 'width': 6016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3960, + 'img_id': 445037074, + 'img_id_str': '445037074', + 'title': '', + 'user_id': 2747704, + 'width': 5921 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3487, + 'img_id': 568114332, + 'img_id_str': '568114332', + 'title': '', + 'user_id': 2747704, + 'width': 5223 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3668, + 'img_id': 82427134, + 'img_id_str': '82427134', + 'title': '', + 'user_id': 2747704, + 'width': 4288 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '16', + 'passed_time': '2019年11月09日', + 'post_id': 58153228, + 'published_at': '2019-11-09 13:38:17', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 14, + 'site': { + 'description': '资深视频创作者', + 'domain': 'pangchen.tuchong.com', + 'followers': 5678, + 'has_everphoto_note': false, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2747704_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影师鹏少', + 'site_id': '2747704', + 'type': 'user', + 'url': 'https://pangchen.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深视频创作者', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2747704', + 'sites': [], + 'tags': ['图虫·OPENSEE2019', '人像', '美女', '小清新', '逆光', '制服'], + 'title': '下课铃响了。', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://pangchen.tuchong.com/58153228/', + 'views': 15050 + }, + { + 'author_id': '46448', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 43, + 'content': 'Grand City-E-scape.\n城市气息系列\n\n摄于东京/深圳/北京/厦门/南京/澳门/上海/首尔', + 'created_at': '2019-12-04 22:22:36', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['2019胶片摄影赛'], + 'excerpt': 'Grand City-E-scape.\n城市气息系列\n\n摄于东京/深圳/北京/厦门/南京/澳门/上海/首尔', + 'favorite_list_prefix': [], + 'favorites': 444, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6452, + 'img_id': 355320475, + 'img_id_str': '355320475', + 'title': '', + 'user_id': 46448, + 'width': 3179 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3556, + 'img_id': 40097884, + 'img_id_str': '40097884', + 'title': '', + 'user_id': 46448, + 'width': 2368 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3181, + 'img_id': 593674271, + 'img_id_str': '593674271', + 'title': '', + 'user_id': 46448, + 'width': 2122 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4184, + 'img_id': 273728165, + 'img_id_str': '273728165', + 'title': '', + 'user_id': 46448, + 'width': 2795 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3680, + 'img_id': 352567862, + 'img_id_str': '352567862', + 'title': '', + 'user_id': 46448, + 'width': 2456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2948, + 'img_id': 138134338, + 'img_id_str': '138134338', + 'title': '', + 'user_id': 46448, + 'width': 4416 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4416, + 'img_id': 279233126, + 'img_id_str': '279233126', + 'title': '', + 'user_id': 46448, + 'width': 2948 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3798, + 'img_id': 551928216, + 'img_id_str': '551928216', + 'title': '', + 'user_id': 46448, + 'width': 2848 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5152, + 'img_id': 335266137, + 'img_id_str': '335266137', + 'title': '', + 'user_id': 46448, + 'width': 3439 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '43', + 'passed_time': '2019年12月04日', + 'post_id': 59891088, + 'published_at': '2019-12-04 22:22:36', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 37, + 'site': { + 'description': '资深风光摄影师', + 'domain': 'stanleychen.tuchong.com', + 'followers': 59762, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_46448_13', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '陈曦Stanley', + 'site_id': '46448', + 'type': 'user', + 'url': 'https://stanleychen.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '46448', + 'sites': [], + 'tags': ['2019胶片摄影赛', '风光', '旅行', '索尼'], + 'title': 'Grand City-E-scape(全集)', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://stanleychen.tuchong.com/59891088/', + 'views': 12361 + }, + { + 'author_id': '4011742', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': '出境:田心源\n裙子:娃娃屋', + 'created_at': '2019-12-25 18:05:55', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '我要上开屏', + '我要上“首页推荐位”', + '捕捉最萌瞬间', + 'Cosplay摄影集中地', + '分享神仙颜值', + '让摄影穿破次元壁', + '人像爱好者', + '绝不停止记录的2019', + '街拍俱乐部圈子' + ], + 'excerpt': '出境:田心源\n裙子:娃娃屋', + 'favorite_list_prefix': [], + 'favorites': 39, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2667, + 'img_id': 342344545, + 'img_id_str': '342344545', + 'title': '294057', + 'user_id': 4011742, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2667, + 'img_id': 222741649, + 'img_id_str': '222741649', + 'title': '294058', + 'user_id': 4011742, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 561497769, + 'img_id_str': '561497769', + 'title': '294060', + 'user_id': 4011742, + 'width': 2667 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2667, + 'img_id': 508806363, + 'img_id_str': '508806363', + 'title': '294061', + 'user_id': 4011742, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 308004312, + 'img_id_str': '308004312', + 'title': '294059', + 'user_id': 4011742, + 'width': 2667 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 396543172, + 'img_id_str': '396543172', + 'title': '294062', + 'user_id': 4011742, + 'width': 2667 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2667, + 'img_id': 631424201, + 'img_id_str': '631424201', + 'title': '294064', + 'user_id': 4011742, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2667, + 'img_id': 471123298, + 'img_id_str': '471123298', + 'title': '294063', + 'user_id': 4011742, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 37340310, + 'img_id_str': '37340310', + 'title': '294056', + 'user_id': 4011742, + 'width': 2667 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '2019年12月25日', + 'post_id': 60966008, + 'published_at': '2019-12-25 18:05:55', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 2, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 1652, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_4011742_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '貉子', + 'site_id': '4011742', + 'type': 'user', + 'url': 'https://tuchong.com/4011742/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '4011742', + 'sites': [], + 'tags': [ + '我要上开屏', + '我要上“首页推荐位”', + '捕捉最萌瞬间', + 'Cosplay摄影集中地', + '分享神仙颜值', + '让摄影穿破次元壁' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/4011742/60966008/', + 'views': 1575 + }, + { + 'author_id': '2759139', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 9, + 'content': '选10张照片总结一下自己的2019', + 'created_at': '2019-12-24 13:30:05', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['2019你最满意的照片'], + 'excerpt': '选10张照片总结一下自己的2019', + 'favorite_list_prefix': [], + 'favorites': 87, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 162112480, + 'img_id_str': '162112480', + 'title': '', + 'user_id': 2759139, + 'width': 1282 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1239, + 'img_id': 424624846, + 'img_id_str': '424624846', + 'title': '', + 'user_id': 2759139, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1157, + 'img_id': 346015652, + 'img_id_str': '346015652', + 'title': '', + 'user_id': 2759139, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1280, + 'img_id': 568113964, + 'img_id_str': '568113964', + 'title': '', + 'user_id': 2759139, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1285, + 'img_id': 42118004, + 'img_id_str': '42118004', + 'title': '', + 'user_id': 2759139, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1153, + 'img_id': 653964909, + 'img_id_str': '653964909', + 'title': '', + 'user_id': 2759139, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 124109573, + 'img_id_str': '124109573', + 'title': '', + 'user_id': 2759139, + 'width': 1230 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 508477411, + 'img_id_str': '508477411', + 'title': '', + 'user_id': 2759139, + 'width': 1266 + }, + { + 'description': '', + 'excerpt': '', + 'height': 977, + 'img_id': 251464723, + 'img_id_str': '251464723', + 'title': '', + 'user_id': 2759139, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1383, + 'img_id': 546771245, + 'img_id_str': '546771245', + 'title': '', + 'user_id': 2759139, + 'width': 1920 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '9', + 'passed_time': '2019年12月24日', + 'post_id': 60913037, + 'published_at': '2019-12-24 13:30:05', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 6, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 3727, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2759139_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '老虎不凶', + 'site_id': '2759139', + 'type': 'user', + 'url': 'https://tuchong.com/2759139/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2759139', + 'sites': [], + 'tags': ['2019你最满意的照片', '风光', '色彩', '2019', '总结', '回顾'], + 'title': '总结2019', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2759139/60913037/', + 'views': 2649 + }, + { + 'author_id': '43625', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 50, + 'content': '', + 'created_at': '2019-10-04 00:27:06', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 880, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2560, + 'img_id': 446934482, + 'img_id_str': '446934482', + 'title': '', + 'user_id': 43625, + 'width': 1706 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1706, + 'img_id': 590785509, + 'img_id_str': '590785509', + 'title': '', + 'user_id': 43625, + 'width': 2560 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1706, + 'img_id': 113552786, + 'img_id_str': '113552786', + 'title': '', + 'user_id': 43625, + 'width': 2560 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2560, + 'img_id': 181251218, + 'img_id_str': '181251218', + 'title': '', + 'user_id': 43625, + 'width': 1706 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1706, + 'img_id': 500018035, + 'img_id_str': '500018035', + 'title': '', + 'user_id': 43625, + 'width': 2560 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1706, + 'img_id': 433630141, + 'img_id_str': '433630141', + 'title': '', + 'user_id': 43625, + 'width': 2560 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1706, + 'img_id': 126659882, + 'img_id_str': '126659882', + 'title': '', + 'user_id': 43625, + 'width': 2560 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2560, + 'img_id': 577416397, + 'img_id_str': '577416397', + 'title': '', + 'user_id': 43625, + 'width': 1706 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1706, + 'img_id': 172534868, + 'img_id_str': '172534868', + 'title': '', + 'user_id': 43625, + 'width': 2560 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1706, + 'img_id': 47426601, + 'img_id_str': '47426601', + 'title': '', + 'user_id': 43625, + 'width': 2560 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1706, + 'img_id': 95071167, + 'img_id_str': '95071167', + 'title': '', + 'user_id': 43625, + 'width': 2560 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '50', + 'passed_time': '2019年10月04日', + 'post_id': 54499087, + 'published_at': '2019-10-04 00:27:06', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 40, + 'site': { + 'description': '', + 'domain': '', + 'followers': 3169, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_43625_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '要有光', + 'site_id': '43625', + 'type': 'user', + 'url': 'https://tuchong.com/43625/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '43625', + 'sites': [], + 'tags': ['色彩', '人像', '旅行', '小清新', '美女', '少女'], + 'title': '看海(九)', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/43625/54499087/', + 'views': 56026 + }, + { + 'author_id': '14404610', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 54, + 'content': '', + 'created_at': '2019-09-15 00:09:51', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1462, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 575972339, + 'img_id_str': '575972339', + 'title': '', + 'user_id': 14404610, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5358, + 'img_id': 64857058, + 'img_id_str': '64857058', + 'title': '', + 'user_id': 14404610, + 'width': 3577 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5452, + 'img_id': 439198604, + 'img_id_str': '439198604', + 'title': '', + 'user_id': 14404610, + 'width': 3640 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5773, + 'img_id': 108700393, + 'img_id_str': '108700393', + 'title': '', + 'user_id': 14404610, + 'width': 3854 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5981, + 'img_id': 237937407, + 'img_id_str': '237937407', + 'title': '', + 'user_id': 14404610, + 'width': 3993 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6016, + 'img_id': 328115065, + 'img_id_str': '328115065', + 'title': '', + 'user_id': 14404610, + 'width': 4016 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5725, + 'img_id': 362128596, + 'img_id_str': '362128596', + 'title': '', + 'user_id': 14404610, + 'width': 3822 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3507, + 'img_id': 246457215, + 'img_id_str': '246457215', + 'title': '', + 'user_id': 14404610, + 'width': 3494 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4737, + 'img_id': 48407787, + 'img_id_str': '48407787', + 'title': '', + 'user_id': 14404610, + 'width': 3162 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '54', + 'passed_time': '2019年09月15日', + 'post_id': 52626566, + 'published_at': '2019-09-15 00:09:51', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 47, + 'site': { + 'description': '越南籍商业摄影师, 曾为BAZAAR拍摄封面', + 'domain': '', + 'followers': 7415, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_14404610_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '_minhnhon_', + 'site_id': '14404610', + 'type': 'user', + 'url': 'https://tuchong.com/14404610/', + 'verification_list': [ + { + 'verification_reason': '越南籍商业摄影师, 曾为BAZAAR拍摄封面', + 'verification_type': 12 + } + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '14404610', + 'sites': [], + 'tags': [], + 'title': 'Our City', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/14404610/52626566/', + 'views': 66903 + }, + { + 'author_id': '2445806', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 18, + 'content': '红房间', + 'created_at': '2019-09-04 09:20:25', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['深圳小清新摄影小组', '富士摄影吧小分队', '我要上开屏'], + 'excerpt': '红房间', + 'favorite_list_prefix': [], + 'favorites': 537, + 'image_count': 23, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 38903340, + 'img_id_str': '38903340', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 208380044, + 'img_id_str': '208380044', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 54238712, + 'img_id_str': '54238712', + 'title': '001', + 'user_id': 2445806, + 'width': 2700 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 617979170, + 'img_id_str': '617979170', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 75407047, + 'img_id_str': '75407047', + 'title': '001', + 'user_id': 2445806, + 'width': 2700 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 322805121, + 'img_id_str': '322805121', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 425958737, + 'img_id_str': '425958737', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 254450925, + 'img_id_str': '254450925', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 53583283, + 'img_id_str': '53583283', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 275226123, + 'img_id_str': '275226123', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 419470527, + 'img_id_str': '419470527', + 'title': '001', + 'user_id': 2445806, + 'width': 2700 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 189111620, + 'img_id_str': '189111620', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 437558427, + 'img_id_str': '437558427', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 207134257, + 'img_id_str': '207134257', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 521576110, + 'img_id_str': '521576110', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 401186580, + 'img_id_str': '401186580', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 427532052, + 'img_id_str': '427532052', + 'title': '001', + 'user_id': 2445806, + 'width': 2700 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 170171726, + 'img_id_str': '170171726', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 132226651, + 'img_id_str': '132226651', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 153656765, + 'img_id_str': '153656765', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 60791882, + 'img_id_str': '60791882', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 337157893, + 'img_id_str': '337157893', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2700, + 'img_id': 549100943, + 'img_id_str': '549100943', + 'title': '001', + 'user_id': 2445806, + 'width': 1800 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '16', + 'passed_time': '2019年09月04日', + 'post_id': 51601078, + 'published_at': '2019-09-04 09:20:25', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 30, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 6010, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2445806_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '太太太洋', + 'site_id': '2445806', + 'type': 'user', + 'url': 'https://tuchong.com/2445806/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2445806', + 'sites': [], + 'tags': ['深圳小清新摄影小组', '富士摄影吧小分队', '我要上开屏', '人像', '暗房'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2445806/51601078/', + 'views': 25774 + }, + { + 'author_id': '3274149', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 24, + 'content': '柔软\n摄影后期:此岸', + 'created_at': '2019-08-14 12:07:03', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '柔软\n摄影后期:此岸', + 'favorite_list_prefix': [], + 'favorites': 1162, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 58232645, + 'img_id_str': '58232645', + 'title': '001', + 'user_id': 3274149, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 184062092, + 'img_id_str': '184062092', + 'title': '001', + 'user_id': 3274149, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 193629700, + 'img_id_str': '193629700', + 'title': '001', + 'user_id': 3274149, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 596414402, + 'img_id_str': '596414402', + 'title': '001', + 'user_id': 3274149, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 494899041, + 'img_id_str': '494899041', + 'title': '001', + 'user_id': 3274149, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 579178166, + 'img_id_str': '579178166', + 'title': '001', + 'user_id': 3274149, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 393449141, + 'img_id_str': '393449141', + 'title': '001', + 'user_id': 3274149, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 648384279, + 'img_id_str': '648384279', + 'title': '001', + 'user_id': 3274149, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 187928378, + 'img_id_str': '187928378', + 'title': '001', + 'user_id': 3274149, + 'width': 7952 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '23', + 'passed_time': '2019年08月14日', + 'post_id': 49117252, + 'published_at': '2019-08-14 12:07:03', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 42, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 20450, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3274149_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '方叉子', + 'site_id': '3274149', + 'type': 'user', + 'url': 'https://tuchong.com/3274149/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3274149', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3274149/49117252/', + 'views': 60086 + }, + { + 'author_id': '3212049', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 35, + 'content': '', + 'created_at': '2019-10-24 17:49:15', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 443, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 401587094, + 'img_id_str': '401587094', + 'title': '', + 'user_id': 3212049, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2800, + 'img_id': 576108082, + 'img_id_str': '576108082', + 'title': '', + 'user_id': 3212049, + 'width': 2100 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 202683773, + 'img_id_str': '202683773', + 'title': '', + 'user_id': 3212049, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2100, + 'img_id': 211924246, + 'img_id_str': '211924246', + 'title': '', + 'user_id': 3212049, + 'width': 2803 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 606385802, + 'img_id_str': '606385802', + 'title': '', + 'user_id': 3212049, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 62240834, + 'img_id_str': '62240834', + 'title': '', + 'user_id': 3212049, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 81311171, + 'img_id_str': '81311171', + 'title': '', + 'user_id': 3212049, + 'width': 3000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 565425765, + 'img_id_str': '565425765', + 'title': '', + 'user_id': 3212049, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 428062685, + 'img_id_str': '428062685', + 'title': '', + 'user_id': 3212049, + 'width': 2000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '31', + 'passed_time': '2019年10月24日', + 'post_id': 56822528, + 'published_at': '2019-10-24 17:49:15', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 14, + 'site': { + 'description': '商业摄影师,欢迎约拍。', + 'domain': '', + 'followers': 11345, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3212049_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '唯剑', + 'site_id': '3212049', + 'type': 'user', + 'url': 'https://tuchong.com/3212049/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '3212049', + 'sites': [], + 'tags': ['人像'], + 'title': '绿林女汉子', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3212049/56822528/', + 'views': 27997 + }, + { + 'author_id': '333261', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 88, + 'content': '拍自于9月坝上游学团。这次游学起早贪黑拍日出,日落拍过瘾了。\n欢迎参加11月金秋徽州游学。', + 'created_at': '2019-10-11 08:34:34', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '拍自于9月坝上游学团。这次游学起早贪黑拍日出,日落拍过瘾了。\n欢迎参加11月金秋徽州游学。', + 'favorite_list_prefix': [], + 'favorites': 1321, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 199143555, + 'img_id_str': '199143555', + 'title': '', + 'user_id': 333261, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 309441006, + 'img_id_str': '309441006', + 'title': '', + 'user_id': 333261, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 625717753, + 'img_id_str': '625717753', + 'title': '', + 'user_id': 333261, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 47427785, + 'img_id_str': '47427785', + 'title': '', + 'user_id': 333261, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 139899270, + 'img_id_str': '139899270', + 'title': '', + 'user_id': 333261, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 621130013, + 'img_id_str': '621130013', + 'title': '', + 'user_id': 333261, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 132558540, + 'img_id_str': '132558540', + 'title': '', + 'user_id': 333261, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 810, + 'img_id': 528396570, + 'img_id_str': '528396570', + 'title': '', + 'user_id': 333261, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 146780595, + 'img_id_str': '146780595', + 'title': '', + 'user_id': 333261, + 'width': 1440 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '87', + 'passed_time': '2019年10月11日', + 'post_id': 55540125, + 'published_at': '2019-10-11 08:34:34', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 85, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'alasy.tuchong.com', + 'followers': 28007, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_333261_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '爱良安', + 'site_id': '333261', + 'type': 'user', + 'url': 'https://alasy.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '333261', + 'sites': [], + 'tags': ['人像', '人像摄影', '古风'], + 'title': '短歌行', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://alasy.tuchong.com/55540125/', + 'views': 30467 + }, + { + 'author_id': '2299484', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 85, + 'content': '生命中最难的阶段不是没有人懂你,而是你不懂你自己。', + 'created_at': '2019-10-08 12:47:11', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '生命中最难的阶段不是没有人懂你,而是你不懂你自己。', + 'favorite_list_prefix': [], + 'favorites': 2345, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 190099435, + 'img_id_str': '190099435', + 'title': '1129651', + 'user_id': 2299484, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 655208332, + 'img_id_str': '655208332', + 'title': '1129573', + 'user_id': 2299484, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 419016828, + 'img_id_str': '419016828', + 'title': '1129548', + 'user_id': 2299484, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 321957926, + 'img_id_str': '321957926', + 'title': '1129547', + 'user_id': 2299484, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 42643507, + 'img_id_str': '42643507', + 'title': '1129544', + 'user_id': 2299484, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 331132885, + 'img_id_str': '331132885', + 'title': '1129545', + 'user_id': 2299484, + 'width': 3413 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 518238099, + 'img_id_str': '518238099', + 'title': '1129542', + 'user_id': 2299484, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 634629844, + 'img_id_str': '634629844', + 'title': '1129541', + 'user_id': 2299484, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3413, + 'img_id': 545501202, + 'img_id_str': '545501202', + 'title': '1129543', + 'user_id': 2299484, + 'width': 5120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 237023586, + 'img_id_str': '237023586', + 'title': '1129539', + 'user_id': 2299484, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 448376539, + 'img_id_str': '448376539', + 'title': '1129540', + 'user_id': 2299484, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5120, + 'img_id': 447394378, + 'img_id_str': '447394378', + 'title': '1129549', + 'user_id': 2299484, + 'width': 3413 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '67', + 'passed_time': '2019年10月08日', + 'post_id': 55214989, + 'published_at': '2019-10-08 12:47:11', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '4', + 'rqt_id': '', + 'shares': 78, + 'site': { + 'description': '马来西亚 新加坡 厦门 福州 广州 深圳 约拍加我m423736448 同行一概不加', + 'domain': '', + 'followers': 16767, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2299484_6', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影师Moyan', + 'site_id': '2299484', + 'type': 'user', + 'url': 'https://tuchong.com/2299484/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '2299484', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2299484/55214989/', + 'views': 171040 + }, + { + 'author_id': '2587179', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 52, + 'content': '摄影 | Eric-Tsui\n修图 | 修图师杜娟', + 'created_at': '2019-09-23 12:37:11', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '摄影 | Eric-Tsui\n修图 | 修图师杜娟', + 'favorite_list_prefix': [], + 'favorites': 1546, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6582, + 'img_id': 448898487, + 'img_id_str': '448898487', + 'title': '', + 'user_id': 2587179, + 'width': 4388 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 478783503, + 'img_id_str': '478783503', + 'title': '', + 'user_id': 2587179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 244557843, + 'img_id_str': '244557843', + 'title': '', + 'user_id': 2587179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 202090277, + 'img_id_str': '202090277', + 'title': '', + 'user_id': 2587179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6587, + 'img_id': 225683495, + 'img_id_str': '225683495', + 'title': '', + 'user_id': 2587179, + 'width': 4391 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 486123260, + 'img_id_str': '486123260', + 'title': '', + 'user_id': 2587179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 116106888, + 'img_id_str': '116106888', + 'title': '', + 'user_id': 2587179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 478520750, + 'img_id_str': '478520750', + 'title': '', + 'user_id': 2587179, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6570, + 'img_id': 379365035, + 'img_id_str': '379365035', + 'title': '', + 'user_id': 2587179, + 'width': 4380 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6582, + 'img_id': 614901729, + 'img_id_str': '614901729', + 'title': '', + 'user_id': 2587179, + 'width': 4388 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 164996858, + 'img_id_str': '164996858', + 'title': '', + 'user_id': 2587179, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '51', + 'passed_time': '2019年09月23日', + 'post_id': 53358878, + 'published_at': '2019-09-23 12:37:11', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 70, + 'site': { + 'description': '资深修图师', + 'domain': '', + 'followers': 13491, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2587179_7', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '修图培训杜娟', + 'site_id': '2587179', + 'type': 'user', + 'url': 'https://tuchong.com/2587179/', + 'verification_list': [ + {'verification_reason': '资深修图师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2587179', + 'sites': [], + 'tags': ['ps', '修图', '修图培训', '商业修图培训', '明星修图'], + 'title': '艺人《沈梦辰》', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2587179/53358878/', + 'views': 104858 + }, + { + 'author_id': '1827482', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 45, + 'content': '', + 'created_at': '2019-09-21 20:31:49', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['2019胶片摄影赛'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1803, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2963, + 'img_id': 302032856, + 'img_id_str': '302032856', + 'title': '', + 'user_id': 1827482, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2963, + 'img_id': 408462617, + 'img_id_str': '408462617', + 'title': '', + 'user_id': 1827482, + 'width': 1976 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2963, + 'img_id': 309700167, + 'img_id_str': '309700167', + 'title': '', + 'user_id': 1827482, + 'width': 1976 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2963, + 'img_id': 280077952, + 'img_id_str': '280077952', + 'title': '', + 'user_id': 1827482, + 'width': 1976 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2963, + 'img_id': 535603055, + 'img_id_str': '535603055', + 'title': '', + 'user_id': 1827482, + 'width': 1976 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2963, + 'img_id': 321234586, + 'img_id_str': '321234586', + 'title': '', + 'user_id': 1827482, + 'width': 1976 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3427, + 'img_id': 464627073, + 'img_id_str': '464627073', + 'title': '', + 'user_id': 1827482, + 'width': 2284 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3427, + 'img_id': 321496560, + 'img_id_str': '321496560', + 'title': '', + 'user_id': 1827482, + 'width': 2284 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3427, + 'img_id': 194094813, + 'img_id_str': '194094813', + 'title': '', + 'user_id': 1827482, + 'width': 2284 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '39', + 'passed_time': '2019年09月21日', + 'post_id': 53198482, + 'published_at': '2019-09-21 20:31:49', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 40, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'j-leonardo.tuchong.com', + 'followers': 6303, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1827482_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '空镜Titanium', + 'site_id': '1827482', + 'type': 'user', + 'url': 'https://j-leonardo.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1827482', + 'sites': [], + 'tags': ['2019胶片摄影赛', '人像', '色彩', '胶片', '复古'], + 'title': '【 琉璃暮色 】', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://j-leonardo.tuchong.com/53198482/', + 'views': 102699 + }, + { + 'author_id': '1610430', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 60, + 'content': '', + 'created_at': '2019-09-07 15:06:10', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 568, + 'image_count': 20, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 489397980, + 'img_id_str': '489397980', + 'title': '', + 'user_id': 1610430, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5646, + 'img_id': 414097393, + 'img_id_str': '414097393', + 'title': '', + 'user_id': 1610430, + 'width': 3764 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3274, + 'img_id': 454860640, + 'img_id_str': '454860640', + 'title': '', + 'user_id': 1610430, + 'width': 2182 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 54763481, + 'img_id_str': '54763481', + 'title': '', + 'user_id': 1610430, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 76587007, + 'img_id_str': '76587007', + 'title': '', + 'user_id': 1610430, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 164339510, + 'img_id_str': '164339510', + 'title': '', + 'user_id': 1610430, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 378445839, + 'img_id_str': '378445839', + 'title': '', + 'user_id': 1610430, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 458399543, + 'img_id_str': '458399543', + 'title': '', + 'user_id': 1610430, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 168140799, + 'img_id_str': '168140799', + 'title': '', + 'user_id': 1610430, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 155229797, + 'img_id_str': '155229797', + 'title': '', + 'user_id': 1610430, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 583048808, + 'img_id_str': '583048808', + 'title': '', + 'user_id': 1610430, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 200777577, + 'img_id_str': '200777577', + 'title': '', + 'user_id': 1610430, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5479, + 'img_id': 655269494, + 'img_id_str': '655269494', + 'title': '', + 'user_id': 1610430, + 'width': 3653 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7026, + 'img_id': 456564501, + 'img_id_str': '456564501', + 'title': '', + 'user_id': 1610430, + 'width': 4684 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 611164379, + 'img_id_str': '611164379', + 'title': '', + 'user_id': 1610430, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 63676566, + 'img_id_str': '63676566', + 'title': '', + 'user_id': 1610430, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 45457199, + 'img_id_str': '45457199', + 'title': '', + 'user_id': 1610430, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3071, + 'img_id': 106078135, + 'img_id_str': '106078135', + 'title': '', + 'user_id': 1610430, + 'width': 2047 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 553885355, + 'img_id_str': '553885355', + 'title': '', + 'user_id': 1610430, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 113745466, + 'img_id_str': '113745466', + 'title': '', + 'user_id': 1610430, + 'width': 5760 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '42', + 'passed_time': '2019年09月07日', + 'post_id': 51881812, + 'published_at': '2019-09-07 15:06:10', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '8', + 'rqt_id': '', + 'shares': 31, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 3437, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1610430_6', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '咖啡爱上猫', + 'site_id': '1610430', + 'type': 'user', + 'url': 'https://tuchong.com/1610430/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1610430', + 'sites': [], + 'tags': ['人像', '夜景', '日常'], + 'title': '夜', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1610430/51881812/', + 'views': 16717 + }, + { + 'author_id': '1720380', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 22, + 'content': 'cos.王者荣耀甄姬 游园惊梦', + 'created_at': '2019-12-12 20:09:15', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '上海人像圈子', + '古风摄影研习社', + '约拍馆摄影作品投稿', + 'Cosplay摄影集中地', + '同济大学摄影圈', + '高颜值女神聚集地' + ], + 'excerpt': 'cos.王者荣耀甄姬 游园惊梦', + 'favorite_list_prefix': [], + 'favorites': 404, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 74105654, + 'img_id_str': '74105654', + 'title': '1682071', + 'user_id': 1720380, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 652722852, + 'img_id_str': '652722852', + 'title': '1804463', + 'user_id': 1720380, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 581551277, + 'img_id_str': '581551277', + 'title': '1804527', + 'user_id': 1720380, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 110805896, + 'img_id_str': '110805896', + 'title': '1804466', + 'user_id': 1720380, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 297714174, + 'img_id_str': '297714174', + 'title': '1804464', + 'user_id': 1720380, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 163693932, + 'img_id_str': '163693932', + 'title': '1804462', + 'user_id': 1720380, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 195281649, + 'img_id_str': '195281649', + 'title': '1804468', + 'user_id': 1720380, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 286835473, + 'img_id_str': '286835473', + 'title': '1804467', + 'user_id': 1720380, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 94749708, + 'img_id_str': '94749708', + 'title': '1804465', + 'user_id': 1720380, + 'width': 1200 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '22', + 'passed_time': '2019年12月12日', + 'post_id': 60324475, + 'published_at': '2019-12-12 20:09:15', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 14, + 'site': { + 'description': '人像摄影', + 'domain': '', + 'followers': 420, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1720380_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '姹姹', + 'site_id': '1720380', + 'type': 'user', + 'url': 'https://tuchong.com/1720380/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '1720380', + 'sites': [], + 'tags': [ + '上海人像圈子', + '古风摄影研习社', + '约拍馆摄影作品投稿', + 'Cosplay摄影集中地', + '同济大学摄影圈', + '高颜值女神聚集地' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1720380/60324475/', + 'views': 20735 + }, + { + 'author_id': '1409920', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 29, + 'content': '长沙长沙', + 'created_at': '2019-10-20 23:00:59', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['第三届京东摄影金像奖'], + 'excerpt': '长沙长沙', + 'favorite_list_prefix': [], + 'favorites': 325, + 'image_count': 18, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 526299544, + 'img_id_str': '526299544', + 'title': '001', + 'user_id': 1409920, + 'width': 3918 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5696, + 'img_id': 176796218, + 'img_id_str': '176796218', + 'title': '001', + 'user_id': 1409920, + 'width': 3800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3800, + 'img_id': 277787203, + 'img_id_str': '277787203', + 'title': '001', + 'user_id': 1409920, + 'width': 5796 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6042, + 'img_id': 554610777, + 'img_id_str': '554610777', + 'title': '001', + 'user_id': 1409920, + 'width': 3900 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 526299545, + 'img_id_str': '526299545', + 'title': '001', + 'user_id': 1409920, + 'width': 7295 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7952, + 'img_id': 88191057, + 'img_id_str': '88191057', + 'title': '001', + 'user_id': 1409920, + 'width': 5304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 296399784, + 'img_id_str': '296399784', + 'title': '001', + 'user_id': 1409920, + 'width': 4732 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3500, + 'img_id': 180401285, + 'img_id_str': '180401285', + 'title': '001', + 'user_id': 1409920, + 'width': 5647 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5835, + 'img_id': 115259200, + 'img_id_str': '115259200', + 'title': '001', + 'user_id': 1409920, + 'width': 3799 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5056, + 'img_id': 533507089, + 'img_id_str': '533507089', + 'title': '001', + 'user_id': 1409920, + 'width': 7041 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6500, + 'img_id': 346533193, + 'img_id_str': '346533193', + 'title': '001', + 'user_id': 1409920, + 'width': 4336 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3800, + 'img_id': 483767028, + 'img_id_str': '483767028', + 'title': '001', + 'user_id': 1409920, + 'width': 5456 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4583, + 'img_id': 390703961, + 'img_id_str': '390703961', + 'title': '001', + 'user_id': 1409920, + 'width': 7249 + }, + { + 'description': '', + 'excerpt': '', + 'height': 7547, + 'img_id': 561687784, + 'img_id_str': '561687784', + 'title': '001', + 'user_id': 1409920, + 'width': 4591 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4630, + 'img_id': 509914898, + 'img_id_str': '509914898', + 'title': '001', + 'user_id': 1409920, + 'width': 7179 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5304, + 'img_id': 295938718, + 'img_id_str': '295938718', + 'title': '001', + 'user_id': 1409920, + 'width': 7952 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3700, + 'img_id': 41726357, + 'img_id_str': '41726357', + 'title': '001', + 'user_id': 1409920, + 'width': 5546 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5080, + 'img_id': 215396998, + 'img_id_str': '215396998', + 'title': '001', + 'user_id': 1409920, + 'width': 7380 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '25', + 'passed_time': '2019年10月20日', + 'post_id': 56505427, + 'published_at': '2019-10-20 23:00:59', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 31, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 7398, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1409920_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '老白屹_', + 'site_id': '1409920', + 'type': 'user', + 'url': 'https://tuchong.com/1409920/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1409920', + 'sites': [], + 'tags': ['第三届京东摄影金像奖', 'JD看城市', '建筑物', '安全'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1409920/56505427/', + 'views': 13338 + }, + { + 'author_id': '3738183', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 68, + 'content': '分享一下我的客人', + 'created_at': '2019-10-18 21:21:11', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['我要上“首页推荐位”'], + 'excerpt': '分享一下我的客人', + 'favorite_list_prefix': [], + 'favorites': 2297, + 'image_count': 4, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5250, + 'img_id': 111391874, + 'img_id_str': '111391874', + 'title': '814114', + 'user_id': 3738183, + 'width': 3500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5250, + 'img_id': 344438049, + 'img_id_str': '344438049', + 'title': '814113', + 'user_id': 3738183, + 'width': 3500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5250, + 'img_id': 468497222, + 'img_id_str': '468497222', + 'title': '814112', + 'user_id': 3738183, + 'width': 3500 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5250, + 'img_id': 539276352, + 'img_id_str': '539276352', + 'title': '814111', + 'user_id': 3738183, + 'width': 3500 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '67', + 'passed_time': '2019年10月18日', + 'post_id': 56261682, + 'published_at': '2019-10-18 21:21:11', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 77, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 15518, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3738183_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '钟月月', + 'site_id': '3738183', + 'type': 'user', + 'url': 'https://tuchong.com/3738183/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3738183', + 'sites': [], + 'tags': ['我要上“首页推荐位”', '人像', '日系', '写真'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3738183/56261682/', + 'views': 77745 + }, + { + 'author_id': '2600543', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 46, + 'content': '出境:@迷你鹿er', + 'created_at': '2019-10-15 17:36:58', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['Cosplay摄影集中地', '让摄影穿破次元壁', '人像写真精选', '第三届京东摄影金像奖'], + 'excerpt': '出境:@迷你鹿er', + 'favorite_list_prefix': [], + 'favorites': 1155, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 581087865, + 'img_id_str': '581087865', + 'title': '', + 'user_id': 2600543, + 'width': 2176 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 453686242, + 'img_id_str': '453686242', + 'title': '', + 'user_id': 2600543, + 'width': 2176 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 212316674, + 'img_id_str': '212316674', + 'title': '', + 'user_id': 2600543, + 'width': 2176 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 473609234, + 'img_id_str': '473609234', + 'title': '', + 'user_id': 2600543, + 'width': 2070 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3290, + 'img_id': 320647903, + 'img_id_str': '320647903', + 'title': '', + 'user_id': 2600543, + 'width': 2164 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3290, + 'img_id': 56537924, + 'img_id_str': '56537924', + 'title': '', + 'user_id': 2600543, + 'width': 2160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 137343615, + 'img_id_str': '137343615', + 'title': '', + 'user_id': 2600543, + 'width': 2176 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2176, + 'img_id': 636203808, + 'img_id_str': '636203808', + 'title': '', + 'user_id': 2600543, + 'width': 3264 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2176, + 'img_id': 35304620, + 'img_id_str': '35304620', + 'title': '', + 'user_id': 2600543, + 'width': 3264 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '44', + 'passed_time': '2019年10月15日', + 'post_id': 55974541, + 'published_at': '2019-10-15 17:36:58', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 35, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': 'sanyue015.tuchong.com', + 'followers': 13819, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2600543_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '叁月life', + 'site_id': '2600543', + 'type': 'user', + 'url': 'https://sanyue015.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2600543', + 'sites': [], + 'tags': ['Cosplay摄影集中地', '让摄影穿破次元壁', '人像写真精选', '第三届京东摄影金像奖', '人像', '写真'], + 'title': '#碧蓝航线 #爱宕 #婚纱', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://sanyue015.tuchong.com/55974541/', + 'views': 50221 + }, + { + 'author_id': '1531311', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 28, + 'content': '', + 'created_at': '2019-09-07 12:48:58', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 727, + 'image_count': 27, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2572, + 'img_id': 534224989, + 'img_id_str': '534224989', + 'title': '', + 'user_id': 1531311, + 'width': 1819 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 373923671, + 'img_id_str': '373923671', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2048, + 'img_id': 306225262, + 'img_id_str': '306225262', + 'title': '', + 'user_id': 1531311, + 'width': 3072 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2048, + 'img_id': 372744054, + 'img_id_str': '372744054', + 'title': '', + 'user_id': 1531311, + 'width': 3072 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 242065047, + 'img_id_str': '242065047', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 171614454, + 'img_id_str': '171614454', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 647667568, + 'img_id_str': '647667568', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2048, + 'img_id': 96051097, + 'img_id_str': '96051097', + 'title': '', + 'user_id': 1531311, + 'width': 3072 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 472424279, + 'img_id_str': '472424279', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 572563178, + 'img_id_str': '572563178', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3072, + 'img_id': 229744458, + 'img_id_str': '229744458', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 422289696, + 'img_id_str': '422289696', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 257072920, + 'img_id_str': '257072920', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 612867701, + 'img_id_str': '612867701', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 85958365, + 'img_id_str': '85958365', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 492544224, + 'img_id_str': '492544224', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 126656450, + 'img_id_str': '126656450', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 435396430, + 'img_id_str': '435396430', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 107060925, + 'img_id_str': '107060925', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3072, + 'img_id': 533897252, + 'img_id_str': '533897252', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3072, + 'img_id': 450797352, + 'img_id_str': '450797352', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 549822329, + 'img_id_str': '549822329', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 215916404, + 'img_id_str': '215916404', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2048, + 'img_id': 630300505, + 'img_id_str': '630300505', + 'title': '', + 'user_id': 1531311, + 'width': 3072 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 323461182, + 'img_id_str': '323461182', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 420323351, + 'img_id_str': '420323351', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2867, + 'img_id': 307077283, + 'img_id_str': '307077283', + 'title': '', + 'user_id': 1531311, + 'width': 2048 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '26', + 'passed_time': '2019年09月07日', + 'post_id': 51870732, + 'published_at': '2019-09-07 12:48:58', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 25, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'wangyimeng.tuchong.com', + 'followers': 11125, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1531311_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '王艺萌', + 'site_id': '1531311', + 'type': 'user', + 'url': 'https://wangyimeng.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1531311', + 'sites': [], + 'tags': ['佳能', '小清新', '日系', '少女', '校园', 'JK'], + 'title': '开学季', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://wangyimeng.tuchong.com/51870732/', + 'views': 20208 + }, + { + 'author_id': '3738183', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 185, + 'content': '送给自己20岁的礼物', + 'created_at': '2019-08-28 23:32:23', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['我要上开屏'], + 'excerpt': '送给自己20岁的礼物', + 'favorite_list_prefix': [], + 'favorites': 2676, + 'image_count': 7, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1334, + 'img_id': 222337530, + 'img_id_str': '222337530', + 'title': '571459', + 'user_id': 3738183, + 'width': 750 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1334, + 'img_id': 551525167, + 'img_id_str': '551525167', + 'title': '571456', + 'user_id': 3738183, + 'width': 750 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1334, + 'img_id': 431332232, + 'img_id_str': '431332232', + 'title': '571457', + 'user_id': 3738183, + 'width': 750 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1334, + 'img_id': 118855879, + 'img_id_str': '118855879', + 'title': '571454', + 'user_id': 3738183, + 'width': 750 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1334, + 'img_id': 224369416, + 'img_id_str': '224369416', + 'title': '571455', + 'user_id': 3738183, + 'width': 750 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1334, + 'img_id': 315136277, + 'img_id_str': '315136277', + 'title': '571458', + 'user_id': 3738183, + 'width': 750 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1334, + 'img_id': 170367481, + 'img_id_str': '170367481', + 'title': '571460', + 'user_id': 3738183, + 'width': 750 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '145', + 'passed_time': '2019年08月28日', + 'post_id': 50964588, + 'published_at': '2019-08-28 23:32:23', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '2', + 'rqt_id': '', + 'shares': 129, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 15518, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3738183_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '钟月月', + 'site_id': '3738183', + 'type': 'user', + 'url': 'https://tuchong.com/3738183/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3738183', + 'sites': [], + 'tags': ['我要上开屏', '服装', '毛衣', '工作室'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3738183/50964588/', + 'views': 92898 + }, + { + 'author_id': '1325971', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 36, + 'content': '', + 'created_at': '2019-08-01 12:47:13', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1249, + 'image_count': 12, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2400, + 'img_id': 322535808, + 'img_id_str': '322535808', + 'title': '', + 'user_id': 1325971, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2400, + 'img_id': 621183700, + 'img_id_str': '621183700', + 'title': '', + 'user_id': 1325971, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2400, + 'img_id': 110789079, + 'img_id_str': '110789079', + 'title': '', + 'user_id': 1325971, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2400, + 'img_id': 454918521, + 'img_id_str': '454918521', + 'title': '', + 'user_id': 1325971, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1067, + 'img_id': 249856378, + 'img_id_str': '249856378', + 'title': '', + 'user_id': 1325971, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2400, + 'img_id': 122126933, + 'img_id_str': '122126933', + 'title': '', + 'user_id': 1325971, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2400, + 'img_id': 365723634, + 'img_id_str': '365723634', + 'title': '', + 'user_id': 1325971, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2400, + 'img_id': 484803120, + 'img_id_str': '484803120', + 'title': '', + 'user_id': 1325971, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1067, + 'img_id': 315654586, + 'img_id_str': '315654586', + 'title': '', + 'user_id': 1325971, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2400, + 'img_id': 364020196, + 'img_id_str': '364020196', + 'title': '', + 'user_id': 1325971, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2400, + 'img_id': 619151737, + 'img_id_str': '619151737', + 'title': '', + 'user_id': 1325971, + 'width': 1600 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2400, + 'img_id': 73171533, + 'img_id_str': '73171533', + 'title': '', + 'user_id': 1325971, + 'width': 1600 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '36', + 'passed_time': '2019年08月01日', + 'post_id': 47181699, + 'published_at': '2019-08-01 12:47:13', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 25, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 2852, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1325971_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '西萌阿兵', + 'site_id': '1325971', + 'type': 'user', + 'url': 'https://tuchong.com/1325971/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1325971', + 'sites': [], + 'tags': ['人像', '少女', '美女', '50mm'], + 'title': '早安,乖乖', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1325971/47181699/', + 'views': 58999 + }, + { + 'author_id': '406880', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 112, + 'content': '', + 'created_at': '2019-07-23 05:49:43', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1605, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 313817664, + 'img_id_str': '313817664', + 'title': '001', + 'user_id': 406880, + 'width': 958 + }, + { + 'description': '', + 'excerpt': '', + 'height': 958, + 'img_id': 519928406, + 'img_id_str': '519928406', + 'title': '001', + 'user_id': 406880, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 958, + 'img_id': 426277499, + 'img_id_str': '426277499', + 'title': '001', + 'user_id': 406880, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 185694895, + 'img_id_str': '185694895', + 'title': '001', + 'user_id': 406880, + 'width': 958 + }, + { + 'description': '', + 'excerpt': '', + 'height': 958, + 'img_id': 214661447, + 'img_id_str': '214661447', + 'title': '001', + 'user_id': 406880, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 34568659, + 'img_id_str': '34568659', + 'title': '001', + 'user_id': 406880, + 'width': 958 + }, + { + 'description': '', + 'excerpt': '', + 'height': 958, + 'img_id': 448821613, + 'img_id_str': '448821613', + 'title': '001', + 'user_id': 406880, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 251034022, + 'img_id_str': '251034022', + 'title': '001', + 'user_id': 406880, + 'width': 958 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 399669718, + 'img_id_str': '399669718', + 'title': '001', + 'user_id': 406880, + 'width': 958 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '91', + 'passed_time': '2019年07月23日', + 'post_id': 45795564, + 'published_at': '2019-07-23 05:49:43', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 67, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 24454, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_406880_9', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '风-vision', + 'site_id': '406880', + 'type': 'user', + 'url': 'https://tuchong.com/406880/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '406880', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/406880/45795564/', + 'views': 105946 + }, + { + 'author_id': '1317325', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 26, + 'content': '', + 'created_at': '2020-01-01 19:19:48', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 424, + 'image_count': 156, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 352765572, + 'img_id_str': '352765572', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3596, + 'img_id': 383371796, + 'img_id_str': '383371796', + 'title': '', + 'user_id': 1317325, + 'width': 2406 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 379569663, + 'img_id_str': '379569663', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 574669812, + 'img_id_str': '574669812', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2393, + 'img_id': 494978624, + 'img_id_str': '494978624', + 'title': '', + 'user_id': 1317325, + 'width': 3577 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 37930860, + 'img_id_str': '37930860', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 103204635, + 'img_id_str': '103204635', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 166381707, + 'img_id_str': '166381707', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3553, + 'img_id': 573818737, + 'img_id_str': '573818737', + 'title': '', + 'user_id': 1317325, + 'width': 2377 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 123586311, + 'img_id_str': '123586311', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 307676886, + 'img_id_str': '307676886', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 551994176, + 'img_id_str': '551994176', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2360, + 'img_id': 549373649, + 'img_id_str': '549373649', + 'title': '', + 'user_id': 1317325, + 'width': 3528 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 90228497, + 'img_id_str': '90228497', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 119129777, + 'img_id_str': '119129777', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3473, + 'img_id': 649250029, + 'img_id_str': '649250029', + 'title': '', + 'user_id': 1317325, + 'width': 2323 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 185648300, + 'img_id_str': '185648300', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 381470422, + 'img_id_str': '381470422', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 554616169, + 'img_id_str': '554616169', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 248235000, + 'img_id_str': '248235000', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2385, + 'img_id': 396805517, + 'img_id_str': '396805517', + 'title': '', + 'user_id': 1317325, + 'width': 3519 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2402, + 'img_id': 284477221, + 'img_id_str': '284477221', + 'title': '', + 'user_id': 1317325, + 'width': 3590 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 615761151, + 'img_id_str': '615761151', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 507758030, + 'img_id_str': '507758030', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2405, + 'img_id': 252167834, + 'img_id_str': '252167834', + 'title': '', + 'user_id': 1317325, + 'width': 3595 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 225363509, + 'img_id_str': '225363509', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 504939606, + 'img_id_str': '504939606', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 643614191, + 'img_id_str': '643614191', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 448120233, + 'img_id_str': '448120233', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 264357223, + 'img_id_str': '264357223', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 522766113, + 'img_id_str': '522766113', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 133809889, + 'img_id_str': '133809889', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 408798835, + 'img_id_str': '408798835', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 418694238, + 'img_id_str': '418694238', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 81839611, + 'img_id_str': '81839611', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 110610285, + 'img_id_str': '110610285', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2382, + 'img_id': 299484439, + 'img_id_str': '299484439', + 'title': '', + 'user_id': 1317325, + 'width': 3561 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 42976581, + 'img_id_str': '42976581', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 530236923, + 'img_id_str': '530236923', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 431867053, + 'img_id_str': '431867053', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 296404131, + 'img_id_str': '296404131', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2420, + 'img_id': 95864239, + 'img_id_str': '95864239', + 'title': '', + 'user_id': 1317325, + 'width': 3617 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 619497093, + 'img_id_str': '619497093', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 186173411, + 'img_id_str': '186173411', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 500090433, + 'img_id_str': '500090433', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 315868907, + 'img_id_str': '315868907', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 34064578, + 'img_id_str': '34064578', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 109561871, + 'img_id_str': '109561871', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3388, + 'img_id': 436061879, + 'img_id_str': '436061879', + 'title': '', + 'user_id': 1317325, + 'width': 2400 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2396, + 'img_id': 375309969, + 'img_id_str': '375309969', + 'title': '', + 'user_id': 1317325, + 'width': 3581 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 346015876, + 'img_id_str': '346015876', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 369936245, + 'img_id_str': '369936245', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2416, + 'img_id': 540591705, + 'img_id_str': '540591705', + 'title': '', + 'user_id': 1317325, + 'width': 3612 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 92522097, + 'img_id_str': '92522097', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 203933044, + 'img_id_str': '203933044', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2424, + 'img_id': 613795454, + 'img_id_str': '613795454', + 'title': '', + 'user_id': 1317325, + 'width': 3623 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 561760119, + 'img_id_str': '561760119', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 352306970, + 'img_id_str': '352306970', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2248, + 'img_id': 602523163, + 'img_id_str': '602523163', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 428918388, + 'img_id_str': '428918388', + 'title': '', + 'user_id': 1317325, + 'width': 3608 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 209241612, + 'img_id_str': '209241612', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 249545977, + 'img_id_str': '249545977', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2419, + 'img_id': 580436941, + 'img_id_str': '580436941', + 'title': '', + 'user_id': 1317325, + 'width': 3616 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 279561482, + 'img_id_str': '279561482', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 594986424, + 'img_id_str': '594986424', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 397068049, + 'img_id_str': '397068049', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 398771826, + 'img_id_str': '398771826', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 131188270, + 'img_id_str': '131188270', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 71484751, + 'img_id_str': '71484751', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 227591926, + 'img_id_str': '227591926', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 307217569, + 'img_id_str': '307217569', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 144622952, + 'img_id_str': '144622952', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 615499199, + 'img_id_str': '615499199', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2360, + 'img_id': 410961410, + 'img_id_str': '410961410', + 'title': '', + 'user_id': 1317325, + 'width': 3528 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 61850928, + 'img_id_str': '61850928', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2415, + 'img_id': 523224107, + 'img_id_str': '523224107', + 'title': '', + 'user_id': 1317325, + 'width': 3610 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 627296025, + 'img_id_str': '627296025', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 438290115, + 'img_id_str': '438290115', + 'title': '', + 'user_id': 1317325, + 'width': 2218 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 313837011, + 'img_id_str': '313837011', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 483640695, + 'img_id_str': '483640695', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 628344320, + 'img_id_str': '628344320', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 533448448, + 'img_id_str': '533448448', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 638371104, + 'img_id_str': '638371104', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 639550758, + 'img_id_str': '639550758', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 603964690, + 'img_id_str': '603964690', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 617203501, + 'img_id_str': '617203501', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 389924354, + 'img_id_str': '389924354', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 145934013, + 'img_id_str': '145934013', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 641189901, + 'img_id_str': '641189901', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 604751276, + 'img_id_str': '604751276', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 38192268, + 'img_id_str': '38192268', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 459851283, + 'img_id_str': '459851283', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 341755791, + 'img_id_str': '341755791', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 644335322, + 'img_id_str': '644335322', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 234538530, + 'img_id_str': '234538530', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3402, + 'img_id': 239585095, + 'img_id_str': '239585095', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 407094569, + 'img_id_str': '407094569', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 132630014, + 'img_id_str': '132630014', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 170640695, + 'img_id_str': '170640695', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2365, + 'img_id': 458278360, + 'img_id_str': '458278360', + 'title': '', + 'user_id': 1317325, + 'width': 3535 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 44549357, + 'img_id_str': '44549357', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 450676510, + 'img_id_str': '450676510', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3595, + 'img_id': 276808607, + 'img_id_str': '276808607', + 'title': '', + 'user_id': 1317325, + 'width': 2405 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 239126308, + 'img_id_str': '239126308', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 467650276, + 'img_id_str': '467650276', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 189973714, + 'img_id_str': '189973714', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2133, + 'img_id': 581289264, + 'img_id_str': '581289264', + 'title': '', + 'user_id': 1317325, + 'width': 3551 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 254592462, + 'img_id_str': '254592462', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2398, + 'img_id': 335528862, + 'img_id_str': '335528862', + 'title': '', + 'user_id': 1317325, + 'width': 3584 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2169, + 'img_id': 100124175, + 'img_id_str': '100124175', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 282249200, + 'img_id_str': '282249200', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 253347188, + 'img_id_str': '253347188', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 451003729, + 'img_id_str': '451003729', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 257213902, + 'img_id_str': '257213902', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 149865822, + 'img_id_str': '149865822', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2307, + 'img_id': 321701103, + 'img_id_str': '321701103', + 'title': '', + 'user_id': 1317325, + 'width': 3636 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 589285324, + 'img_id_str': '589285324', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 134923539, + 'img_id_str': '134923539', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 412140623, + 'img_id_str': '412140623', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 477218709, + 'img_id_str': '477218709', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 322946462, + 'img_id_str': '322946462', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 376424066, + 'img_id_str': '376424066', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 124635337, + 'img_id_str': '124635337', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 600557124, + 'img_id_str': '600557124', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 452445555, + 'img_id_str': '452445555', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 568706479, + 'img_id_str': '568706479', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 587711871, + 'img_id_str': '587711871', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 622446192, + 'img_id_str': '622446192', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 375637657, + 'img_id_str': '375637657', + 'title': '', + 'user_id': 1317325, + 'width': 2107 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 277333329, + 'img_id_str': '277333329', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 138200407, + 'img_id_str': '138200407', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 344966443, + 'img_id_str': '344966443', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2391, + 'img_id': 449955449, + 'img_id_str': '449955449', + 'title': '', + 'user_id': 1317325, + 'width': 3574 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 199018289, + 'img_id_str': '199018289', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 204850479, + 'img_id_str': '204850479', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 152225040, + 'img_id_str': '152225040', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2250, + 'img_id': 633587058, + 'img_id_str': '633587058', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3637, + 'img_id': 551405599, + 'img_id_str': '551405599', + 'title': '', + 'user_id': 1317325, + 'width': 2433 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 187221289, + 'img_id_str': '187221289', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 476693827, + 'img_id_str': '476693827', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 79021769, + 'img_id_str': '79021769', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2385, + 'img_id': 572245468, + 'img_id_str': '572245468', + 'title': '', + 'user_id': 1317325, + 'width': 3565 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2402, + 'img_id': 261473805, + 'img_id_str': '261473805', + 'title': '', + 'user_id': 1317325, + 'width': 3590 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2403, + 'img_id': 601277868, + 'img_id_str': '601277868', + 'title': '', + 'user_id': 1317325, + 'width': 3592 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 404014479, + 'img_id_str': '404014479', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2433, + 'img_id': 329893049, + 'img_id_str': '329893049', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2305, + 'img_id': 587056675, + 'img_id_str': '587056675', + 'title': '', + 'user_id': 1317325, + 'width': 3637 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3318, + 'img_id': 152487179, + 'img_id_str': '152487179', + 'title': '', + 'user_id': 1317325, + 'width': 2279 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2279, + 'img_id': 444581362, + 'img_id_str': '444581362', + 'title': '', + 'user_id': 1317325, + 'width': 3318 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2279, + 'img_id': 631358883, + 'img_id_str': '631358883', + 'title': '', + 'user_id': 1317325, + 'width': 3318 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2156, + 'img_id': 514442644, + 'img_id_str': '514442644', + 'title': '', + 'user_id': 1317325, + 'width': 3303 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2279, + 'img_id': 407815364, + 'img_id_str': '407815364', + 'title': '', + 'user_id': 1317325, + 'width': 3318 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3255, + 'img_id': 574669813, + 'img_id_str': '574669813', + 'title': '', + 'user_id': 1317325, + 'width': 2236 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2233, + 'img_id': 624018956, + 'img_id_str': '624018956', + 'title': '', + 'user_id': 1317325, + 'width': 3251 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3242, + 'img_id': 324716075, + 'img_id_str': '324716075', + 'title': '', + 'user_id': 1317325, + 'width': 2227 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2390, + 'img_id': 622249495, + 'img_id_str': '622249495', + 'title': '', + 'user_id': 1317325, + 'width': 3573 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '26', + 'passed_time': '01月01日', + 'post_id': 61292051, + 'published_at': '2020-01-01 19:19:48', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 17, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 16657, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1317325_6', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '蔡九记', + 'site_id': '1317325', + 'type': 'user', + 'url': 'https://tuchong.com/1317325/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1317325', + 'sites': [], + 'tags': [], + 'title': '冲吧冲吧', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1317325/61292051/', + 'views': 21184 + }, + { + 'author_id': '406880', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 40, + 'content': '冬至\n出镜:轩轩', + 'created_at': '2019-12-28 11:07:44', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '冬至\n出镜:轩轩', + 'favorite_list_prefix': [], + 'favorites': 492, + 'image_count': 14, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 551339442, + 'img_id_str': '551339442', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 111199699, + 'img_id_str': '111199699', + 'title': '001', + 'user_id': 406880, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 90948976, + 'img_id_str': '90948976', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 574407786, + 'img_id_str': '574407786', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 140363214, + 'img_id_str': '140363214', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 71943897, + 'img_id_str': '71943897', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 616088906, + 'img_id_str': '616088906', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 59622563, + 'img_id_str': '59622563', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 170837482, + 'img_id_str': '170837482', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 304006409, + 'img_id_str': '304006409', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 217761014, + 'img_id_str': '217761014', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 204326157, + 'img_id_str': '204326157', + 'title': '001', + 'user_id': 406880, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 570017156, + 'img_id_str': '570017156', + 'title': '001', + 'user_id': 406880, + 'width': 960 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1440, + 'img_id': 552256976, + 'img_id_str': '552256976', + 'title': '001', + 'user_id': 406880, + 'width': 960 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '35', + 'passed_time': '2019年12月28日', + 'post_id': 61083981, + 'published_at': '2019-12-28 11:07:44', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 13, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 24454, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_406880_9', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '风-vision', + 'site_id': '406880', + 'type': 'user', + 'url': 'https://tuchong.com/406880/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '406880', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/406880/61083981/', + 'views': 30677 + }, + { + 'author_id': '529822', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 40, + 'content': '那一天 \n你几乎带走我 \n所有 的从前\n故事里\n不再出现与你相关\n 的画面\n一个人的幸福 \n 怎么编', + 'created_at': '2019-12-14 12:36:48', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '2019胶片摄影赛', + '我要上开屏', + '对短发姑娘没有任何抵抗力!', + '分享神仙颜值', + '人像爱好者', + '陕西人像摄影' + ], + 'excerpt': '那一天 \n你几乎带走我 \n所有 的从前\n故事里\n不再出现与你相关\n 的画面\n一个人的幸福 \n 怎么编', + 'favorite_list_prefix': [], + 'favorites': 513, + 'image_count': 15, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 387236717, + 'img_id_str': '387236717', + 'title': '', + 'user_id': 529822, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 270451562, + 'img_id_str': '270451562', + 'title': '', + 'user_id': 529822, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2632, + 'img_id': 102810135, + 'img_id_str': '102810135', + 'title': '', + 'user_id': 529822, + 'width': 4680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 546096033, + 'img_id_str': '546096033', + 'title': '', + 'user_id': 529822, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 361808681, + 'img_id_str': '361808681', + 'title': '', + 'user_id': 529822, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 201704487, + 'img_id_str': '201704487', + 'title': '', + 'user_id': 529822, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 500810605, + 'img_id_str': '500810605', + 'title': '', + 'user_id': 529822, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 155960315, + 'img_id_str': '155960315', + 'title': '', + 'user_id': 529822, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 217564354, + 'img_id_str': '217564354', + 'title': '', + 'user_id': 529822, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 199214191, + 'img_id_str': '199214191', + 'title': '', + 'user_id': 529822, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 54575694, + 'img_id_str': '54575694', + 'title': '', + 'user_id': 529822, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 504021967, + 'img_id_str': '504021967', + 'title': '', + 'user_id': 529822, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 255771657, + 'img_id_str': '255771657', + 'title': '', + 'user_id': 529822, + 'width': 3120 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2767, + 'img_id': 98027132, + 'img_id_str': '98027132', + 'title': '', + 'user_id': 529822, + 'width': 4680 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4680, + 'img_id': 144819348, + 'img_id_str': '144819348', + 'title': '', + 'user_id': 529822, + 'width': 3120 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '21', + 'passed_time': '2019年12月14日', + 'post_id': 60410711, + 'published_at': '2019-12-14 12:36:48', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 17, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'guchengshijue.tuchong.com', + 'followers': 15440, + 'has_everphoto_note': false, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_529822_12', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '孤城视觉', + 'site_id': '529822', + 'type': 'user', + 'url': 'https://guchengshijue.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '529822', + 'sites': [], + 'tags': [ + '2019胶片摄影赛', + '我要上开屏', + '对短发姑娘没有任何抵抗力!', + '分享神仙颜值', + '人像爱好者', + '陕西人像摄影' + ], + 'title': '一点点', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://guchengshijue.tuchong.com/60410711/', + 'views': 18044 + }, + { + 'author_id': '2342168', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 22, + 'content': '阳光透过窗口\n洒进一帘幽梦\n我愿白纱遮眼\n以梦为马\n像一朵云\n掠过风\n掠过树\n掠过你', + 'created_at': '2019-11-20 21:33:25', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['日系集'], + 'excerpt': '阳光透过窗口\n洒进一帘幽梦\n我愿白纱遮眼\n以梦为马\n像一朵云\n掠过风\n掠过树\n掠过你', + 'favorite_list_prefix': [], + 'favorites': 621, + 'image_count': 17, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 496156621, + 'img_id_str': '496156621', + 'title': '', + 'user_id': 2342168, + 'width': 2688 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2523, + 'img_id': 450281452, + 'img_id_str': '450281452', + 'title': '', + 'user_id': 2342168, + 'width': 1802 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 394706451, + 'img_id_str': '394706451', + 'title': '', + 'user_id': 2342168, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1673, + 'img_id': 447921749, + 'img_id_str': '447921749', + 'title': '', + 'user_id': 2342168, + 'width': 1673 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 311869234, + 'img_id_str': '311869234', + 'title': '', + 'user_id': 2342168, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 213696474, + 'img_id_str': '213696474', + 'title': '', + 'user_id': 2342168, + 'width': 2688 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 510312121, + 'img_id_str': '510312121', + 'title': '', + 'user_id': 2342168, + 'width': 1371 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2197, + 'img_id': 541441751, + 'img_id_str': '541441751', + 'title': '', + 'user_id': 2342168, + 'width': 1569 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1371, + 'img_id': 154321150, + 'img_id_str': '154321150', + 'title': '', + 'user_id': 2342168, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1892, + 'img_id': 373211787, + 'img_id_str': '373211787', + 'title': '', + 'user_id': 2342168, + 'width': 2648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 595378132, + 'img_id_str': '595378132', + 'title': '', + 'user_id': 2342168, + 'width': 2688 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2688, + 'img_id': 309248041, + 'img_id_str': '309248041', + 'title': '', + 'user_id': 2342168, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2880, + 'img_id': 69844831, + 'img_id_str': '69844831', + 'title': '', + 'user_id': 2342168, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1741, + 'img_id': 190234450, + 'img_id_str': '190234450', + 'title': '', + 'user_id': 2342168, + 'width': 1741 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1638, + 'img_id': 381140255, + 'img_id_str': '381140255', + 'title': '', + 'user_id': 2342168, + 'width': 1638 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 603438903, + 'img_id_str': '603438903', + 'title': '', + 'user_id': 2342168, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2263, + 'img_id': 38321965, + 'img_id_str': '38321965', + 'title': '', + 'user_id': 2342168, + 'width': 1617 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '22', + 'passed_time': '2019年11月20日', + 'post_id': 58964959, + 'published_at': '2019-11-20 21:33:25', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 17, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 4477, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2342168_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '摄手座DA', + 'site_id': '2342168', + 'type': 'user', + 'url': 'https://tuchong.com/2342168/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2342168', + 'sites': [], + 'tags': ['日系集', '人像', '美女'], + 'title': '以梦为马', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/2342168/58964959/', + 'views': 28795 + }, + { + 'author_id': '3733034', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 16, + 'content': 'FGO正片', + 'created_at': '2019-11-05 15:07:18', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': 'FGO正片', + 'favorite_list_prefix': [], + 'favorites': 670, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3200, + 'img_id': 273922646, + 'img_id_str': '273922646', + 'title': '001', + 'user_id': 3733034, + 'width': 4800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 539212695, + 'img_id_str': '539212695', + 'title': '001', + 'user_id': 3733034, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 634764122, + 'img_id_str': '634764122', + 'title': '001', + 'user_id': 3733034, + 'width': 3200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 595508309, + 'img_id_str': '595508309', + 'title': '001', + 'user_id': 3733034, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 93895026, + 'img_id_str': '93895026', + 'title': '001', + 'user_id': 3733034, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 509000141, + 'img_id_str': '509000141', + 'title': '001', + 'user_id': 3733034, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 66501570, + 'img_id_str': '66501570', + 'title': '001', + 'user_id': 3733034, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 213826272, + 'img_id_str': '213826272', + 'title': '001', + 'user_id': 3733034, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 241285957, + 'img_id_str': '241285957', + 'title': '001', + 'user_id': 3733034, + 'width': 3200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5472, + 'img_id': 330873154, + 'img_id_str': '330873154', + 'title': '001', + 'user_id': 3733034, + 'width': 3648 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4800, + 'img_id': 335198645, + 'img_id_str': '335198645', + 'title': '001', + 'user_id': 3733034, + 'width': 3200 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '16', + 'passed_time': '2019年11月05日', + 'post_id': 57859682, + 'published_at': '2019-11-05 15:07:18', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '9', + 'rqt_id': '', + 'shares': 20, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 6387, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3733034_3', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影愛倫同学', + 'site_id': '3733034', + 'type': 'user', + 'url': 'https://tuchong.com/3733034/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3733034', + 'sites': [], + 'tags': ['Cosplay'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3733034/57859682/', + 'views': 27369 + }, + { + 'author_id': '2976763', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 81, + 'content': 'Photographer:顾小白Hala\nModel:琳琳\nOrganizer:文珺Ronnie', + 'created_at': '2019-10-10 17:15:50', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '高颜值女神聚集地', + '分享神仙颜值', + '人像爱好者', + '糖水人像小组', + '暖色调人像', + '写真人像摄影', + '人像摄影集', + '佳能器材党', + '我们都爱日系摄影', + '我要上开屏', + '绝不停止记录的2019', + '周末好时光' + ], + 'excerpt': 'Photographer:顾小白Hala\nModel:琳琳\nOrganizer:文珺Ronnie', + 'favorite_list_prefix': [], + 'favorites': 1506, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6006, + 'img_id': 196653493, + 'img_id_str': '196653493', + 'title': '', + 'user_id': 2976763, + 'width': 4004 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6288, + 'img_id': 410038363, + 'img_id_str': '410038363', + 'title': '', + 'user_id': 2976763, + 'width': 4192 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 482193776, + 'img_id_str': '482193776', + 'title': '', + 'user_id': 2976763, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6384, + 'img_id': 130330669, + 'img_id_str': '130330669', + 'title': '', + 'user_id': 2976763, + 'width': 4256 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 410890704, + 'img_id_str': '410890704', + 'title': '', + 'user_id': 2976763, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5970, + 'img_id': 377597824, + 'img_id_str': '377597824', + 'title': '', + 'user_id': 2976763, + 'width': 3980 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 633778207, + 'img_id_str': '633778207', + 'title': '', + 'user_id': 2976763, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 48541967, + 'img_id_str': '48541967', + 'title': '', + 'user_id': 2976763, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 538489154, + 'img_id_str': '538489154', + 'title': '', + 'user_id': 2976763, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6198, + 'img_id': 629977420, + 'img_id_str': '629977420', + 'title': '', + 'user_id': 2976763, + 'width': 4132 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '76', + 'passed_time': '2019年10月10日', + 'post_id': 55477651, + 'published_at': '2019-10-10 17:15:50', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 35, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'gxbhala.tuchong.com', + 'followers': 39077, + 'has_everphoto_note': false, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2976763_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '顾小白Hala', + 'site_id': '2976763', + 'type': 'user', + 'url': 'https://gxbhala.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2976763', + 'sites': [], + 'tags': ['高颜值女神聚集地', '分享神仙颜值', '人像爱好者', '糖水人像小组', '暖色调人像', '写真人像摄影'], + 'title': '杨花落尽子规啼', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://gxbhala.tuchong.com/55477651/', + 'views': 67104 + }, + { + 'author_id': '1349328', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 47, + 'content': '光,落在你脸上,可爱一如往常。', + 'created_at': '2019-09-18 17:35:37', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '光,落在你脸上,可爱一如往常。', + 'favorite_list_prefix': [], + 'favorites': 920, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3360, + 'img_id': 198746991, + 'img_id_str': '198746991', + 'title': '', + 'user_id': 1349328, + 'width': 2240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2309, + 'img_id': 516400918, + 'img_id_str': '516400918', + 'title': '', + 'user_id': 1349328, + 'width': 1539 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2888, + 'img_id': 86550008, + 'img_id_str': '86550008', + 'title': '', + 'user_id': 1349328, + 'width': 1926 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2778, + 'img_id': 203137947, + 'img_id_str': '203137947', + 'title': '', + 'user_id': 1349328, + 'width': 1852 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3142, + 'img_id': 359441827, + 'img_id_str': '359441827', + 'title': '', + 'user_id': 1349328, + 'width': 2095 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3165, + 'img_id': 380150695, + 'img_id_str': '380150695', + 'title': '', + 'user_id': 1349328, + 'width': 2110 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2882, + 'img_id': 92841318, + 'img_id_str': '92841318', + 'title': '', + 'user_id': 1349328, + 'width': 1921 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3107, + 'img_id': 532849699, + 'img_id_str': '532849699', + 'title': '', + 'user_id': 1349328, + 'width': 2072 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3360, + 'img_id': 482780953, + 'img_id_str': '482780953', + 'title': '', + 'user_id': 1349328, + 'width': 2240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2452, + 'img_id': 359441826, + 'img_id_str': '359441826', + 'title': '', + 'user_id': 1349328, + 'width': 1635 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '43', + 'passed_time': '2019年09月18日', + 'post_id': 52928737, + 'published_at': '2019-09-18 17:35:37', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 41, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 7901, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1349328_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '壹梵i', + 'site_id': '1349328', + 'type': 'user', + 'url': 'https://tuchong.com/1349328/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1349328', + 'sites': [], + 'tags': ['人像', '佳能', '日系'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1349328/52928737/', + 'views': 28980 + }, + { + 'author_id': '982513', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 20, + 'content': '', + 'created_at': '2019-11-06 19:18:24', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 412, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 406895151, + 'img_id_str': '406895151', + 'title': '', + 'user_id': 982513, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 471906623, + 'img_id_str': '471906623', + 'title': '', + 'user_id': 982513, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 182041329, + 'img_id_str': '182041329', + 'title': '', + 'user_id': 982513, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 237091693, + 'img_id_str': '237091693', + 'title': '', + 'user_id': 982513, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 64207408, + 'img_id_str': '64207408', + 'title': '', + 'user_id': 982513, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 113556549, + 'img_id_str': '113556549', + 'title': '', + 'user_id': 982513, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 114998014, + 'img_id_str': '114998014', + 'title': '', + 'user_id': 982513, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5907, + 'img_id': 215465014, + 'img_id_str': '215465014', + 'title': '', + 'user_id': 982513, + 'width': 3938 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 580762709, + 'img_id_str': '580762709', + 'title': '', + 'user_id': 982513, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '19', + 'passed_time': '2019年11月06日', + 'post_id': 57952462, + 'published_at': '2019-11-06 19:18:24', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 13, + 'site': { + 'description': 'QQ/微信: 341215 欢迎交流/约拍.', + 'domain': '', + 'followers': 4219, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_982513_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'ゞ灵魂', + 'site_id': '982513', + 'type': 'user', + 'url': 'https://tuchong.com/982513/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '982513', + 'sites': [], + 'tags': ['人像', '城市', '美女', '高尔夫'], + 'title': '摄影是件让人愉快的事---Monica', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/982513/57952462/', + 'views': 22268 + }, + { + 'author_id': '1509808', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 59, + 'content': '想立刻搬进你眼中', + 'created_at': '2019-10-24 10:10:46', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['分享神仙颜值'], + 'excerpt': '想立刻搬进你眼中', + 'favorite_list_prefix': [], + 'favorites': 1251, + 'image_count': 13, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 360364066, + 'img_id_str': '360364066', + 'title': '001', + 'user_id': 1509808, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 210744687, + 'img_id_str': '210744687', + 'title': '001', + 'user_id': 1509808, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 532854206, + 'img_id_str': '532854206', + 'title': '001', + 'user_id': 1509808, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 293058488, + 'img_id_str': '293058488', + 'title': '001', + 'user_id': 1509808, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 177190943, + 'img_id_str': '177190943', + 'title': '001', + 'user_id': 1509808, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 240825653, + 'img_id_str': '240825653', + 'title': '001', + 'user_id': 1509808, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 259044765, + 'img_id_str': '259044765', + 'title': '001', + 'user_id': 1509808, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 379696723, + 'img_id_str': '379696723', + 'title': '001', + 'user_id': 1509808, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 348108282, + 'img_id_str': '348108282', + 'title': '001', + 'user_id': 1509808, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 433829334, + 'img_id_str': '433829334', + 'title': '001', + 'user_id': 1509808, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 645379451, + 'img_id_str': '645379451', + 'title': '001', + 'user_id': 1509808, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 417772776, + 'img_id_str': '417772776', + 'title': '001', + 'user_id': 1509808, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1800, + 'img_id': 549303940, + 'img_id_str': '549303940', + 'title': '001', + 'user_id': 1509808, + 'width': 1200 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '59', + 'passed_time': '2019年10月24日', + 'post_id': 56790650, + 'published_at': '2019-10-24 10:10:46', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 73, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 4600, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1509808_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '远远Ayn', + 'site_id': '1509808', + 'type': 'user', + 'url': 'https://tuchong.com/1509808/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1509808', + 'sites': [], + 'tags': ['分享神仙颜值', '人像', '摄影', '少女'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1509808/56790650/', + 'views': 53227 + }, + { + 'author_id': '1733468', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 54, + 'content': '白蛇传之红颜旧', + 'created_at': '2019-09-16 10:11:47', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '白蛇传之红颜旧', + 'favorite_list_prefix': [], + 'favorites': 581, + 'image_count': 16, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2316, + 'img_id': 167814617, + 'img_id_str': '167814617', + 'title': '', + 'user_id': 1733468, + 'width': 1537 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2371, + 'img_id': 604873978, + 'img_id_str': '604873978', + 'title': '', + 'user_id': 1733468, + 'width': 1517 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2287, + 'img_id': 243639424, + 'img_id_str': '243639424', + 'title': '', + 'user_id': 1733468, + 'width': 1443 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2266, + 'img_id': 193701130, + 'img_id_str': '193701130', + 'title': '', + 'user_id': 1733468, + 'width': 1435 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2291, + 'img_id': 481469255, + 'img_id_str': '481469255', + 'title': '', + 'user_id': 1733468, + 'width': 1495 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1482, + 'img_id': 366191715, + 'img_id_str': '366191715', + 'title': '', + 'user_id': 1733468, + 'width': 2357 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1449, + 'img_id': 260744411, + 'img_id_str': '260744411', + 'title': '', + 'user_id': 1733468, + 'width': 2410 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1462, + 'img_id': 622437256, + 'img_id_str': '622437256', + 'title': '', + 'user_id': 1733468, + 'width': 2357 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2323, + 'img_id': 292397797, + 'img_id_str': '292397797', + 'title': '', + 'user_id': 1733468, + 'width': 1484 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1493, + 'img_id': 301966448, + 'img_id_str': '301966448', + 'title': '', + 'user_id': 1733468, + 'width': 2363 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2291, + 'img_id': 486909087, + 'img_id_str': '486909087', + 'title': '', + 'user_id': 1733468, + 'width': 1520 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2268, + 'img_id': 156869367, + 'img_id_str': '156869367', + 'title': '', + 'user_id': 1733468, + 'width': 1511 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1760, + 'img_id': 579642113, + 'img_id_str': '579642113', + 'title': '', + 'user_id': 1733468, + 'width': 1744 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2356, + 'img_id': 316121666, + 'img_id_str': '316121666', + 'title': '', + 'user_id': 1733468, + 'width': 1426 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1446, + 'img_id': 342401844, + 'img_id_str': '342401844', + 'title': '', + 'user_id': 1733468, + 'width': 2357 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2262, + 'img_id': 64070460, + 'img_id_str': '64070460', + 'title': '', + 'user_id': 1733468, + 'width': 1522 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '50', + 'passed_time': '2019年09月16日', + 'post_id': 52743756, + 'published_at': '2019-09-16 10:11:47', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 35, + 'site': { + 'description': '', + 'domain': '', + 'followers': 4350, + 'has_everphoto_note': false, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1733468_4', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '心是自在A', + 'site_id': '1733468', + 'type': 'user', + 'url': 'https://tuchong.com/1733468/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '1733468', + 'sites': [], + 'tags': ['人像'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1733468/52743756/', + 'views': 41356 + }, + { + 'author_id': '1303010', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 37, + 'content': '', + 'created_at': '2019-08-31 08:29:37', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 771, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 356555965, + 'img_id_str': '356555965', + 'title': '', + 'user_id': 1303010, + 'width': 1333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1125, + 'img_id': 591961520, + 'img_id_str': '591961520', + 'title': '', + 'user_id': 1303010, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 296328686, + 'img_id_str': '296328686', + 'title': '', + 'user_id': 1303010, + 'width': 1333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 549165862, + 'img_id_str': '549165862', + 'title': '', + 'user_id': 1303010, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 422288829, + 'img_id_str': '422288829', + 'title': '', + 'user_id': 1303010, + 'width': 2000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 63216338, + 'img_id_str': '63216338', + 'title': '', + 'user_id': 1303010, + 'width': 1320 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 503553070, + 'img_id_str': '503553070', + 'title': '', + 'user_id': 1303010, + 'width': 1333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 560962303, + 'img_id_str': '560962303', + 'title': '', + 'user_id': 1303010, + 'width': 1333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2000, + 'img_id': 305372418, + 'img_id_str': '305372418', + 'title': '', + 'user_id': 1303010, + 'width': 1333 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1333, + 'img_id': 220110471, + 'img_id_str': '220110471', + 'title': '', + 'user_id': 1303010, + 'width': 2000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '34', + 'passed_time': '2019年08月31日', + 'post_id': 51190423, + 'published_at': '2019-08-31 08:29:37', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 12, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': 'devilkman.tuchong.com', + 'followers': 4653, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1303010_3', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '咕哒囧子', + 'site_id': '1303010', + 'type': 'user', + 'url': 'https://devilkman.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1303010', + 'sites': [], + 'tags': [], + 'title': '去年花博会拍的', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://devilkman.tuchong.com/51190423/', + 'views': 27040 + }, + { + 'author_id': '3274149', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 21, + 'content': '秘密花园\n摄影后期:此岸', + 'created_at': '2019-08-14 15:57:26', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '秘密花园\n摄影后期:此岸', + 'favorite_list_prefix': [], + 'favorites': 815, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 198086385, + 'img_id_str': '198086385', + 'title': '001', + 'user_id': 3274149, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 350195257, + 'img_id_str': '350195257', + 'title': '001', + 'user_id': 3274149, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 166629262, + 'img_id_str': '166629262', + 'title': '001', + 'user_id': 3274149, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 458919916, + 'img_id_str': '458919916', + 'title': '001', + 'user_id': 3274149, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 404787189, + 'img_id_str': '404787189', + 'title': '001', + 'user_id': 3274149, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 512790192, + 'img_id_str': '512790192', + 'title': '001', + 'user_id': 3274149, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 563384313, + 'img_id_str': '563384313', + 'title': '001', + 'user_id': 3274149, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6000, + 'img_id': 251760421, + 'img_id_str': '251760421', + 'title': '001', + 'user_id': 3274149, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 484347701, + 'img_id_str': '484347701', + 'title': '001', + 'user_id': 3274149, + 'width': 6000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '21', + 'passed_time': '2019年08月14日', + 'post_id': 49145624, + 'published_at': '2019-08-14 15:57:26', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 13, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 20450, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3274149_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '方叉子', + 'site_id': '3274149', + 'type': 'user', + 'url': 'https://tuchong.com/3274149/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3274149', + 'sites': [], + 'tags': [], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3274149/49145624/', + 'views': 49961 + }, + { + 'author_id': '3262232', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 33, + 'content': '王羽衫', + 'created_at': '2019-07-30 17:18:42', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '王羽衫', + 'favorite_list_prefix': [], + 'favorites': 968, + 'image_count': 6, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6463, + 'img_id': 448168437, + 'img_id_str': '448168437', + 'title': '001', + 'user_id': 3262232, + 'width': 4310 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 328630832, + 'img_id_str': '328630832', + 'title': '001', + 'user_id': 3262232, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 85557701, + 'img_id_str': '85557701', + 'title': '001', + 'user_id': 3262232, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 642547982, + 'img_id_str': '642547982', + 'title': '001', + 'user_id': 3262232, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6401, + 'img_id': 540639695, + 'img_id_str': '540639695', + 'title': '001', + 'user_id': 3262232, + 'width': 4267 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 408191476, + 'img_id_str': '408191476', + 'title': '001', + 'user_id': 3262232, + 'width': 4480 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '27', + 'passed_time': '2019年07月30日', + 'post_id': 46927932, + 'published_at': '2019-07-30 17:18:42', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '28', + 'rqt_id': '', + 'shares': 17, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 16485, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3262232_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影师刘逸凡', + 'site_id': '3262232', + 'type': 'user', + 'url': 'https://tuchong.com/3262232/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3262232', + 'sites': [], + 'tags': ['人像', '佳能', '少女', '小清新', '日系', '可爱'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3262232/46927932/', + 'views': 39785 + }, + { + 'author_id': '1311838', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': 'PHOTO BY ANDYWAN', + 'created_at': '2020-01-08 02:40:46', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['古典蓝晒作品征集'], + 'excerpt': 'PHOTO BY ANDYWAN', + 'favorite_list_prefix': [], + 'favorites': 33, + 'image_count': 11, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5448, + 'img_id': 603505967, + 'img_id_str': '603505967', + 'title': '', + 'user_id': 1311838, + 'width': 3632 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 608486957, + 'img_id_str': '608486957', + 'title': '', + 'user_id': 1311838, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3073, + 'img_id': 548718112, + 'img_id_str': '548718112', + 'title': '', + 'user_id': 1311838, + 'width': 3073 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3840, + 'img_id': 479119563, + 'img_id_str': '479119563', + 'title': '', + 'user_id': 1311838, + 'width': 5760 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3767, + 'img_id': 572376494, + 'img_id_str': '572376494', + 'title': '', + 'user_id': 1311838, + 'width': 5651 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3771, + 'img_id': 361613159, + 'img_id_str': '361613159', + 'title': '', + 'user_id': 1311838, + 'width': 5656 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3479, + 'img_id': 162121031, + 'img_id_str': '162121031', + 'title': '', + 'user_id': 1311838, + 'width': 5218 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3689, + 'img_id': 53593512, + 'img_id_str': '53593512', + 'title': '', + 'user_id': 1311838, + 'width': 5533 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3555, + 'img_id': 412468559, + 'img_id_str': '412468559', + 'title': '', + 'user_id': 1311838, + 'width': 5332 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5476, + 'img_id': 653379260, + 'img_id_str': '653379260', + 'title': '', + 'user_id': 1311838, + 'width': 3651 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 357877258, + 'img_id_str': '357877258', + 'title': '', + 'user_id': 1311838, + 'width': 3840 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月08日', + 'post_id': 61504851, + 'published_at': '2020-01-08 02:40:46', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'andywan.tuchong.com', + 'followers': 682, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1311838_4', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '安迪andywan', + 'site_id': '1311838', + 'type': 'user', + 'url': 'https://andywan.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1311838', + 'sites': [], + 'tags': ['古典蓝晒作品征集', '人像', '色彩', '女孩', '写真'], + 'title': '沉醉于深蓝之海', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://andywan.tuchong.com/61504851/', + 'views': 1193 + }, + { + 'author_id': '388420', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 69, + 'content': '璀璨入梦,星光入海。\n出镜:鲸鱼\n出品:有映画工作室', + 'created_at': '2019-12-05 16:37:29', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '璀璨入梦,星光入海。\n出镜:鲸鱼\n出品:有映画工作室', + 'favorite_list_prefix': [], + 'favorites': 985, + 'image_count': 17, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 250397277, + 'img_id_str': '250397277', + 'title': '', + 'user_id': 388420, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1199, + 'img_id': 215007504, + 'img_id_str': '215007504', + 'title': '', + 'user_id': 388420, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1199, + 'img_id': 245547635, + 'img_id_str': '245547635', + 'title': '', + 'user_id': 388420, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1199, + 'img_id': 434291176, + 'img_id_str': '434291176', + 'title': '', + 'user_id': 388420, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1199, + 'img_id': 221430317, + 'img_id_str': '221430317', + 'title': '', + 'user_id': 388420, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1199, + 'img_id': 337298007, + 'img_id_str': '337298007', + 'title': '', + 'user_id': 388420, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1199, + 'img_id': 223723662, + 'img_id_str': '223723662', + 'title': '', + 'user_id': 388420, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1199, + 'img_id': 200065785, + 'img_id_str': '200065785', + 'title': '', + 'user_id': 388420, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1199, + 'img_id': 209568402, + 'img_id_str': '209568402', + 'title': '', + 'user_id': 388420, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 46187254, + 'img_id_str': '46187254', + 'title': '', + 'user_id': 388420, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 309576983, + 'img_id_str': '309576983', + 'title': '', + 'user_id': 388420, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1199, + 'img_id': 238142088, + 'img_id_str': '238142088', + 'title': '', + 'user_id': 388420, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1199, + 'img_id': 359449438, + 'img_id_str': '359449438', + 'title': '', + 'user_id': 388420, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1199, + 'img_id': 226476611, + 'img_id_str': '226476611', + 'title': '', + 'user_id': 388420, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 263963612, + 'img_id_str': '263963612', + 'title': '', + 'user_id': 388420, + 'width': 1200 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1199, + 'img_id': 454279699, + 'img_id_str': '454279699', + 'title': '', + 'user_id': 388420, + 'width': 800 + }, + { + 'description': '', + 'excerpt': '', + 'height': 800, + 'img_id': 135315905, + 'img_id_str': '135315905', + 'title': '', + 'user_id': 388420, + 'width': 1200 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '55', + 'passed_time': '2019年12月05日', + 'post_id': 59924656, + 'published_at': '2019-12-05 16:37:29', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 30, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'zwyoyi.tuchong.com', + 'followers': 9535, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_388420_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '游弋yoyi', + 'site_id': '388420', + 'type': 'user', + 'url': 'https://zwyoyi.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '388420', + 'sites': [], + 'tags': ['人像', '美女', '海边', '日落', '烟花'], + 'title': '【有映画】星海', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://zwyoyi.tuchong.com/59924656/', + 'views': 32745 + }, + { + 'author_id': '2574940', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 44, + 'content': '', + 'created_at': '2019-11-08 12:27:50', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 1062, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 591706813, + 'img_id_str': '591706813', + 'title': '', + 'user_id': 2574940, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 287881850, + 'img_id_str': '287881850', + 'title': '', + 'user_id': 2574940, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4333, + 'img_id': 351910885, + 'img_id_str': '351910885', + 'title': '', + 'user_id': 2574940, + 'width': 6499 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6558, + 'img_id': 614972223, + 'img_id_str': '614972223', + 'title': '', + 'user_id': 2574940, + 'width': 4372 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4928, + 'img_id': 148945675, + 'img_id_str': '148945675', + 'title': '', + 'user_id': 2574940, + 'width': 3264 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 257997289, + 'img_id_str': '257997289', + 'title': '', + 'user_id': 2574940, + 'width': 6720 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6720, + 'img_id': 293387692, + 'img_id_str': '293387692', + 'title': '', + 'user_id': 2574940, + 'width': 4480 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3188, + 'img_id': 434944824, + 'img_id_str': '434944824', + 'title': '', + 'user_id': 2574940, + 'width': 4813 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4928, + 'img_id': 338869040, + 'img_id_str': '338869040', + 'title': '', + 'user_id': 2574940, + 'width': 3264 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '42', + 'passed_time': '2019年11月08日', + 'post_id': 58071420, + 'published_at': '2019-11-08 12:27:50', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '19', + 'rqt_id': '', + 'shares': 40, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'mixmico.tuchong.com', + 'followers': 48506, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2574940_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'MixMico米扣', + 'site_id': '2574940', + 'type': 'user', + 'url': 'https://mixmico.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2574940', + 'sites': [], + 'tags': ['人像', '色彩', '写真', '少女', '女孩', '小清新'], + 'title': '少女A', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://mixmico.tuchong.com/58071420/', + 'views': 60357 + }, + { + 'author_id': '3738183', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 96, + 'content': '分享一下我的客人', + 'created_at': '2019-10-24 19:17:17', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['高颜值女神聚集地'], + 'excerpt': '分享一下我的客人', + 'favorite_list_prefix': [], + 'favorites': 2113, + 'image_count': 4, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 188594039, + 'img_id_str': '188594039', + 'title': '821493', + 'user_id': 3738183, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1504, + 'img_id': 184137181, + 'img_id_str': '184137181', + 'title': '836714', + 'user_id': 3738183, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1620, + 'img_id': 157333128, + 'img_id_str': '157333128', + 'title': '821491', + 'user_id': 3738183, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1504, + 'img_id': 630503029, + 'img_id_str': '630503029', + 'title': '821492', + 'user_id': 3738183, + 'width': 1080 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '91', + 'passed_time': '2019年10月24日', + 'post_id': 56829592, + 'published_at': '2019-10-24 19:17:17', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 83, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 15518, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3738183_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '钟月月', + 'site_id': '3738183', + 'type': 'user', + 'url': 'https://tuchong.com/3738183/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3738183', + 'sites': [], + 'tags': ['高颜值女神聚集地', '人像', '日系'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3738183/56829592/', + 'views': 88274 + }, + { + 'author_id': '1827482', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 5, + 'content': '', + 'created_at': '2019-10-01 01:55:13', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['胶片是种情怀'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 415, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5477, + 'img_id': 283749100, + 'img_id_str': '283749100', + 'title': '', + 'user_id': 1827482, + 'width': 3651 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2067, + 'img_id': 530688758, + 'img_id_str': '530688758', + 'title': '', + 'user_id': 1827482, + 'width': 2067 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5477, + 'img_id': 412855019, + 'img_id_str': '412855019', + 'title': '', + 'user_id': 1827482, + 'width': 3651 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5477, + 'img_id': 281717486, + 'img_id_str': '281717486', + 'title': '', + 'user_id': 1827482, + 'width': 3651 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5477, + 'img_id': 649178099, + 'img_id_str': '649178099', + 'title': '', + 'user_id': 1827482, + 'width': 3651 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5477, + 'img_id': 213953472, + 'img_id_str': '213953472', + 'title': '', + 'user_id': 1827482, + 'width': 3651 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4875, + 'img_id': 284470057, + 'img_id_str': '284470057', + 'title': '', + 'user_id': 1827482, + 'width': 3250 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5477, + 'img_id': 102935246, + 'img_id_str': '102935246', + 'title': '', + 'user_id': 1827482, + 'width': 3651 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5477, + 'img_id': 464169886, + 'img_id_str': '464169886', + 'title': '', + 'user_id': 1827482, + 'width': 3651 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '5', + 'passed_time': '2019年10月01日', + 'post_id': 54102726, + 'published_at': '2019-10-01 01:55:13', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 6, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'j-leonardo.tuchong.com', + 'followers': 6303, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1827482_5', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '空镜Titanium', + 'site_id': '1827482', + 'type': 'user', + 'url': 'https://j-leonardo.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1827482', + 'sites': [], + 'tags': ['胶片是种情怀', '色彩', '人像', '胶片', '复古'], + 'title': '【鱼】', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://j-leonardo.tuchong.com/54102726/', + 'views': 17417 + }, + { + 'author_id': '3262232', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 22, + 'content': '王羽衫', + 'created_at': '2019-08-03 17:59:18', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '王羽衫', + 'favorite_list_prefix': [], + 'favorites': 700, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2176, + 'img_id': 300975187, + 'img_id_str': '300975187', + 'title': '001', + 'user_id': 3262232, + 'width': 3264 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 46957875, + 'img_id_str': '46957875', + 'title': '001', + 'user_id': 3262232, + 'width': 2176 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2176, + 'img_id': 327058864, + 'img_id_str': '327058864', + 'title': '001', + 'user_id': 3262232, + 'width': 3264 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 637961569, + 'img_id_str': '637961569', + 'title': '001', + 'user_id': 3262232, + 'width': 2176 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 589399637, + 'img_id_str': '589399637', + 'title': '001', + 'user_id': 3262232, + 'width': 2176 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 291734632, + 'img_id_str': '291734632', + 'title': '001', + 'user_id': 3262232, + 'width': 2176 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 359957699, + 'img_id_str': '359957699', + 'title': '001', + 'user_id': 3262232, + 'width': 2176 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 99255536, + 'img_id_str': '99255536', + 'title': '001', + 'user_id': 3262232, + 'width': 2176 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3264, + 'img_id': 384730558, + 'img_id_str': '384730558', + 'title': '001', + 'user_id': 3262232, + 'width': 2176 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '20', + 'passed_time': '2019年08月03日', + 'post_id': 47505333, + 'published_at': '2019-08-03 17:59:18', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '15', + 'rqt_id': '', + 'shares': 20, + 'site': { + 'description': '资深Cosplay摄影师', + 'domain': '', + 'followers': 16485, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_3262232_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影师刘逸凡', + 'site_id': '3262232', + 'type': 'user', + 'url': 'https://tuchong.com/3262232/', + 'verification_list': [ + {'verification_reason': '资深Cosplay摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '3262232', + 'sites': [], + 'tags': ['人像', '佳能', '色彩', '少女', '小清新', '日系'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/3262232/47505333/', + 'views': 26897 + }, + { + 'author_id': '10592504', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 60, + 'content': '夜空', + 'created_at': '2019-08-01 21:52:54', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '夜空', + 'favorite_list_prefix': [], + 'favorites': 1296, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 2304, + 'img_id': 151290718, + 'img_id_str': '151290718', + 'title': '001', + 'user_id': 10592504, + 'width': 2304 + }, + { + 'description': '', + 'excerpt': '', + 'height': 613, + 'img_id': 147424094, + 'img_id_str': '147424094', + 'title': '001', + 'user_id': 10592504, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1152, + 'img_id': 211846183, + 'img_id_str': '211846183', + 'title': '001', + 'user_id': 10592504, + 'width': 1728 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1428, + 'img_id': 255361990, + 'img_id_str': '255361990', + 'title': '001', + 'user_id': 10592504, + 'width': 2540 + }, + { + 'description': '', + 'excerpt': '', + 'height': 813, + 'img_id': 297698021, + 'img_id_str': '297698021', + 'title': '001', + 'user_id': 10592504, + 'width': 1518 + }, + { + 'description': '', + 'excerpt': '', + 'height': 972, + 'img_id': 507216814, + 'img_id_str': '507216814', + 'title': '001', + 'user_id': 10592504, + 'width': 1728 + }, + { + 'description': '', + 'excerpt': '', + 'height': 960, + 'img_id': 635404931, + 'img_id_str': '635404931', + 'title': '001', + 'user_id': 10592504, + 'width': 1440 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1152, + 'img_id': 147162286, + 'img_id_str': '147162286', + 'title': '001', + 'user_id': 10592504, + 'width': 1152 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1152, + 'img_id': 343901477, + 'img_id_str': '343901477', + 'title': '001', + 'user_id': 10592504, + 'width': 1152 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '59', + 'passed_time': '2019年08月01日', + 'post_id': 47259435, + 'published_at': '2019-08-01 21:52:54', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 34, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 12006, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_10592504_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '摄影师陌年', + 'site_id': '10592504', + 'type': 'user', + 'url': 'https://tuchong.com/10592504/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '10592504', + 'sites': [], + 'tags': ['人像', '海滩', '写真', '美女人像', '人像摄影', '夜景人像'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/10592504/47259435/', + 'views': 58292 + }, + { + 'author_id': '1413882', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 20, + 'content': 'girl and snack', + 'created_at': '2019-11-30 12:04:28', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '驚鴻·一眸', + '写真人像摄影', + '成都约拍圈子', + '成都人像爱好者', + '高颜值女神聚集地', + '日系集', + '分享神仙颜值' + ], + 'excerpt': 'girl and snack', + 'favorite_list_prefix': [], + 'favorites': 335, + 'image_count': 6, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3666, + 'img_id': 469352787, + 'img_id_str': '469352787', + 'title': '001', + 'user_id': 1413882, + 'width': 5499 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5320, + 'img_id': 412205535, + 'img_id_str': '412205535', + 'title': '001', + 'user_id': 1413882, + 'width': 3547 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5186, + 'img_id': 638763146, + 'img_id_str': '638763146', + 'title': '001', + 'user_id': 1413882, + 'width': 3457 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5688, + 'img_id': 186237318, + 'img_id_str': '186237318', + 'title': '001', + 'user_id': 1413882, + 'width': 3792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 508281036, + 'img_id_str': '508281036', + 'title': '001', + 'user_id': 1413882, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4513, + 'img_id': 221691930, + 'img_id_str': '221691930', + 'title': '001', + 'user_id': 1413882, + 'width': 3009 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '20', + 'passed_time': '2019年11月30日', + 'post_id': 59609903, + 'published_at': '2019-11-30 12:04:28', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 7, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 2548, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1413882_3', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': 'Batkid', + 'site_id': '1413882', + 'type': 'user', + 'url': 'https://tuchong.com/1413882/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1413882', + 'sites': [], + 'tags': ['驚鴻·一眸', '写真人像摄影', '成都约拍圈子', '成都人像爱好者', '高颜值女神聚集地', '日系集'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1413882/59609903/', + 'views': 13186 + }, + { + 'author_id': '490904', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 37, + 'content': '星河滚烫,你是人间理想', + 'created_at': '2019-11-17 19:57:57', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '冷色调人像', + '有温度的人像', + '人像写真摄影约拍', + '分享神仙颜值', + '驚鴻·一眸', + '约拍馆摄影作品投稿', + '糖水人像小组', + '人像爱好者', + '河北摄影圈', + '高颜值女神聚集地' + ], + 'excerpt': '星河滚烫,你是人间理想', + 'favorite_list_prefix': [], + 'favorites': 1266, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 461749811, + 'img_id_str': '461749811', + 'title': '1130431', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 583122316, + 'img_id_str': '583122316', + 'title': '1130433', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 234274446, + 'img_id_str': '234274446', + 'title': '1130425', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 111394458, + 'img_id_str': '111394458', + 'title': '1130428', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 177782842, + 'img_id_str': '177782842', + 'title': '1130427', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 154779232, + 'img_id_str': '154779232', + 'title': '1130430', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 85704816, + 'img_id_str': '85704816', + 'title': '1130426', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 443989844, + 'img_id_str': '443989844', + 'title': '1130432', + 'user_id': 490904, + 'width': 1000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1500, + 'img_id': 42188213, + 'img_id_str': '42188213', + 'title': '1130429', + 'user_id': 490904, + 'width': 1000 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '37', + 'passed_time': '2019年11月17日', + 'post_id': 58769071, + 'published_at': '2019-11-17 19:57:57', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 48, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 12649, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_490904_8', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '摄影师CAT', + 'site_id': '490904', + 'type': 'user', + 'url': 'https://tuchong.com/490904/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '490904', + 'sites': [], + 'tags': ['冷色调人像', '有温度的人像', '人像写真摄影约拍', '分享神仙颜值', '驚鴻·一眸', '约拍馆摄影作品投稿'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/490904/58769071/', + 'views': 39431 + }, + { + 'author_id': '1124419', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 19, + 'content': '', + 'created_at': '2019-10-28 15:43:07', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 397, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5792, + 'img_id': 364426855, + 'img_id_str': '364426855', + 'title': '', + 'user_id': 1124419, + 'width': 8688 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 135444506, + 'img_id_str': '135444506', + 'title': '', + 'user_id': 1124419, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4930, + 'img_id': 273397522, + 'img_id_str': '273397522', + 'title': '', + 'user_id': 1124419, + 'width': 7395 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 317896303, + 'img_id_str': '317896303', + 'title': '', + 'user_id': 1124419, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5615, + 'img_id': 412727302, + 'img_id_str': '412727302', + 'title': '', + 'user_id': 1124419, + 'width': 8423 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 469808894, + 'img_id_str': '469808894', + 'title': '', + 'user_id': 1124419, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 306755106, + 'img_id_str': '306755106', + 'title': '', + 'user_id': 1124419, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 281131082, + 'img_id_str': '281131082', + 'title': '', + 'user_id': 1124419, + 'width': 5792 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8688, + 'img_id': 601798555, + 'img_id_str': '601798555', + 'title': '', + 'user_id': 1124419, + 'width': 5792 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '19', + 'passed_time': '2019年10月28日', + 'post_id': 57176330, + 'published_at': '2019-10-28 15:43:07', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 9, + 'site': { + 'description': '资深人像摄影师', + 'domain': '215447352.tuchong.com', + 'followers': 9016, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1124419_3', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '夏雨天的X视界', + 'site_id': '1124419', + 'type': 'user', + 'url': 'https://215447352.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1124419', + 'sites': [], + 'tags': ['美腿', '长腿', '红色高跟鞋', '旗袍', '适马', '135art'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://215447352.tuchong.com/57176330/', + 'views': 11392 + }, + { + 'author_id': '1844143', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 10, + 'content': '#contracted#', + 'created_at': '2019-10-23 15:33:31', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '#contracted#', + 'favorite_list_prefix': [], + 'favorites': 301, + 'image_count': 5, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 491959937, + 'img_id_str': '491959937', + 'title': '3686306', + 'user_id': 1844143, + 'width': 2984 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3922, + 'img_id': 362656734, + 'img_id_str': '362656734', + 'title': '3686305', + 'user_id': 1844143, + 'width': 2812 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 82818717, + 'img_id_str': '82818717', + 'title': '3686317', + 'user_id': 1844143, + 'width': 2984 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 465745930, + 'img_id_str': '465745930', + 'title': '3686319', + 'user_id': 1844143, + 'width': 2984 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4480, + 'img_id': 268940861, + 'img_id_str': '268940861', + 'title': '3686318', + 'user_id': 1844143, + 'width': 2984 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '10', + 'passed_time': '2019年10月23日', + 'post_id': 56729678, + 'published_at': '2019-10-23 15:33:31', + 'recommend': true, + 'recom_type': '', + 'rewardable': false, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 8, + 'site': { + 'description': '', + 'domain': '', + 'followers': 3469, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1844143_4', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'Glong_Q', + 'site_id': '1844143', + 'type': 'user', + 'url': 'https://tuchong.com/1844143/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '1844143', + 'sites': [], + 'tags': ['优雅', '女人', '女孩', '人像', '工作室', '时尚'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1844143/56729678/', + 'views': 10300 + }, + { + 'author_id': '1409920', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 93, + 'content': '武汉武汉', + 'created_at': '2019-10-20 23:14:52', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['第三届京东摄影金像奖'], + 'excerpt': '武汉武汉', + 'favorite_list_prefix': [], + 'favorites': 1237, + 'image_count': 28, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 493921208, + 'img_id_str': '493921208', + 'title': '001', + 'user_id': 1409920, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3325, + 'img_id': 617965156, + 'img_id_str': '617965156', + 'title': '001', + 'user_id': 1409920, + 'width': 3197 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5168, + 'img_id': 455350227, + 'img_id_str': '455350227', + 'title': '001', + 'user_id': 1409920, + 'width': 4000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3640, + 'img_id': 348497315, + 'img_id_str': '348497315', + 'title': '001', + 'user_id': 1409920, + 'width': 2675 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 579253774, + 'img_id_str': '579253774', + 'title': '001', + 'user_id': 1409920, + 'width': 4051 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5328, + 'img_id': 433491816, + 'img_id_str': '433491816', + 'title': '001', + 'user_id': 1409920, + 'width': 3161 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5186, + 'img_id': 201624800, + 'img_id_str': '201624800', + 'title': '001', + 'user_id': 1409920, + 'width': 3312 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4489, + 'img_id': 522750244, + 'img_id_str': '522750244', + 'title': '001', + 'user_id': 1409920, + 'width': 2918 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3510, + 'img_id': 418799358, + 'img_id_str': '418799358', + 'title': '001', + 'user_id': 1409920, + 'width': 5260 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4927, + 'img_id': 42770833, + 'img_id_str': '42770833', + 'title': '001', + 'user_id': 1409920, + 'width': 2867 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8249, + 'img_id': 171285733, + 'img_id_str': '171285733', + 'title': '001', + 'user_id': 1409920, + 'width': 2732 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4129, + 'img_id': 331326047, + 'img_id_str': '331326047', + 'title': '001', + 'user_id': 1409920, + 'width': 6037 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4571, + 'img_id': 106344646, + 'img_id_str': '106344646', + 'title': '001', + 'user_id': 1409920, + 'width': 3179 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4948, + 'img_id': 332695939, + 'img_id_str': '332695939', + 'title': '001', + 'user_id': 1409920, + 'width': 2718 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8352, + 'img_id': 445035368, + 'img_id_str': '445035368', + 'title': '001', + 'user_id': 1409920, + 'width': 2993 + }, + { + 'description': '', + 'excerpt': '', + 'height': 9343, + 'img_id': 592360546, + 'img_id_str': '592360546', + 'title': '001', + 'user_id': 1409920, + 'width': 2671 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3640, + 'img_id': 390952117, + 'img_id_str': '390952117', + 'title': '001', + 'user_id': 1409920, + 'width': 4699 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3806, + 'img_id': 263353245, + 'img_id_str': '263353245', + 'title': '001', + 'user_id': 1409920, + 'width': 6240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3000, + 'img_id': 220555895, + 'img_id_str': '220555895', + 'title': '001', + 'user_id': 1409920, + 'width': 1026 + }, + { + 'description': '', + 'excerpt': '', + 'height': 8192, + 'img_id': 191870954, + 'img_id_str': '191870954', + 'title': '001', + 'user_id': 1409920, + 'width': 2501 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3454, + 'img_id': 347321582, + 'img_id_str': '347321582', + 'title': '001', + 'user_id': 1409920, + 'width': 4044 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6511, + 'img_id': 477345535, + 'img_id_str': '477345535', + 'title': '001', + 'user_id': 1409920, + 'width': 2458 + }, + { + 'description': '', + 'excerpt': '', + 'height': 4000, + 'img_id': 68465710, + 'img_id_str': '68465710', + 'title': '001', + 'user_id': 1409920, + 'width': 6000 + }, + { + 'description': '', + 'excerpt': '', + 'height': 541, + 'img_id': 155694356, + 'img_id_str': '155694356', + 'title': '001', + 'user_id': 1409920, + 'width': 750 + }, + { + 'description': '', + 'excerpt': '', + 'height': 536, + 'img_id': 252294658, + 'img_id_str': '252294658', + 'title': '001', + 'user_id': 1409920, + 'width': 750 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3745, + 'img_id': 123052725, + 'img_id_str': '123052725', + 'title': '001', + 'user_id': 1409920, + 'width': 5445 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5199, + 'img_id': 392017197, + 'img_id_str': '392017197', + 'title': '001', + 'user_id': 1409920, + 'width': 3167 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6047, + 'img_id': 568239704, + 'img_id_str': '568239704', + 'title': '001', + 'user_id': 1409920, + 'width': 4160 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '87', + 'passed_time': '2019年10月20日', + 'post_id': 56507376, + 'published_at': '2019-10-20 23:14:52', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 56, + 'site': { + 'description': '资深风光摄影师', + 'domain': '', + 'followers': 7398, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1409920_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '老白屹_', + 'site_id': '1409920', + 'type': 'user', + 'url': 'https://tuchong.com/1409920/', + 'verification_list': [ + {'verification_reason': '资深风光摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '1409920', + 'sites': [], + 'tags': ['第三届京东摄影金像奖', 'JD看城市'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1409920/56507376/', + 'views': 33253 + }, + { + 'author_id': '2661362', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 45, + 'content': '小言', + 'created_at': '2019-09-26 21:14:58', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['上海外拍活动'], + 'excerpt': '小言', + 'favorite_list_prefix': [], + 'favorites': 834, + 'image_count': 15, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 480617808, + 'img_id_str': '480617808', + 'title': '001', + 'user_id': 2661362, + 'width': 1239 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1231, + 'img_id': 381986107, + 'img_id_str': '381986107', + 'title': '001', + 'user_id': 2661362, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1281, + 'img_id': 387163067, + 'img_id_str': '387163067', + 'title': '001', + 'user_id': 2661362, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1260, + 'img_id': 346727742, + 'img_id_str': '346727742', + 'title': '001', + 'user_id': 2661362, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 134718765, + 'img_id_str': '134718765', + 'title': '001', + 'user_id': 2661362, + 'width': 1281 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1286, + 'img_id': 172991992, + 'img_id_str': '172991992', + 'title': '001', + 'user_id': 2661362, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 450668015, + 'img_id_str': '450668015', + 'title': '001', + 'user_id': 2661362, + 'width': 1281 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 484484422, + 'img_id_str': '484484422', + 'title': '001', + 'user_id': 2661362, + 'width': 1281 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1281, + 'img_id': 346792956, + 'img_id_str': '346792956', + 'title': '001', + 'user_id': 2661362, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 301966715, + 'img_id_str': '301966715', + 'title': '001', + 'user_id': 2661362, + 'width': 1306 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 652060066, + 'img_id_str': '652060066', + 'title': '001', + 'user_id': 2661362, + 'width': 1281 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 42247717, + 'img_id_str': '42247717', + 'title': '001', + 'user_id': 2661362, + 'width': 1241 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1315, + 'img_id': 287024176, + 'img_id_str': '287024176', + 'title': '001', + 'user_id': 2661362, + 'width': 1920 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 266511650, + 'img_id_str': '266511650', + 'title': '001', + 'user_id': 2661362, + 'width': 1202 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1920, + 'img_id': 68069170, + 'img_id_str': '68069170', + 'title': '001', + 'user_id': 2661362, + 'width': 1281 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '40', + 'passed_time': '2019年09月26日', + 'post_id': 53649839, + 'published_at': '2019-09-26 21:14:58', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 20, + 'site': { + 'description': '资深人像摄影师', + 'domain': '18921935500.tuchong.com', + 'followers': 4499, + 'has_everphoto_note': false, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_2661362_2', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '克林', + 'site_id': '2661362', + 'type': 'user', + 'url': 'https://18921935500.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '2661362', + 'sites': [], + 'tags': ['上海外拍活动', '小言', '人像', '美女', '时尚', '黑白'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://18921935500.tuchong.com/53649839/', + 'views': 34955 + }, + { + 'author_id': '35928', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 25, + 'content': '', + 'created_at': '2019-08-03 11:38:14', + 'data_type': 'post', + 'delete': false, + 'event_tags': [], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 643, + 'image_count': 9, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 851, + 'img_id': 353011283, + 'img_id_str': '353011283', + 'title': '', + 'user_id': 35928, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 792, + 'img_id': 126387603, + 'img_id_str': '126387603', + 'title': '', + 'user_id': 35928, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1294, + 'img_id': 607552474, + 'img_id_str': '607552474', + 'title': '', + 'user_id': 35928, + 'width': 859 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1297, + 'img_id': 261064348, + 'img_id_str': '261064348', + 'title': '', + 'user_id': 35928, + 'width': 859 + }, + { + 'description': '', + 'excerpt': '', + 'height': 851, + 'img_id': 178816002, + 'img_id_str': '178816002', + 'title': '', + 'user_id': 35928, + 'width': 1280 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1293, + 'img_id': 452428911, + 'img_id_str': '452428911', + 'title': '', + 'user_id': 35928, + 'width': 975 + }, + { + 'description': '', + 'excerpt': '', + 'height': 756, + 'img_id': 516260924, + 'img_id_str': '516260924', + 'title': '', + 'user_id': 35928, + 'width': 1132 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1297, + 'img_id': 91129279, + 'img_id_str': '91129279', + 'title': '', + 'user_id': 35928, + 'width': 856 + }, + { + 'description': '', + 'excerpt': '', + 'height': 852, + 'img_id': 308249876, + 'img_id_str': '308249876', + 'title': '', + 'user_id': 35928, + 'width': 1280 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '21', + 'passed_time': '2019年08月03日', + 'post_id': 47456212, + 'published_at': '2019-08-03 11:38:14', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 18, + 'site': { + 'description': '资深人像摄影师', + 'domain': 'bubblegirl.tuchong.com', + 'followers': 4235, + 'has_everphoto_note': true, + 'icon': + 'https://sf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_35928_11', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': 'FAYE-HSU', + 'site_id': '35928', + 'type': 'user', + 'url': 'https://bubblegirl.tuchong.com/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '35928', + 'sites': [], + 'tags': ['人像'], + 'title': '小功写真', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://bubblegirl.tuchong.com/47456212/', + 'views': 29048 + }, + { + 'author_id': '15957713', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '延安乾坤湾', + 'created_at': '2020-01-19 15:41:43', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['最满意的风光照'], + 'excerpt': '延安乾坤湾', + 'favorite_list_prefix': [], + 'favorites': 12, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 843, + 'img_id': 654559599, + 'img_id_str': '654559599', + 'title': '001', + 'user_id': 15957713, + 'width': 1125 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '2', + 'passed_time': '01月19日', + 'post_id': 61839750, + 'published_at': '2020-01-19 15:41:43', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '爱摄影的小工程师', + 'domain': '', + 'followers': 19, + 'has_everphoto_note': true, + 'icon': + 'https://lf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15957713_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '萱鲍勃', + 'site_id': '15957713', + 'type': 'user', + 'url': 'https://tuchong.com/15957713/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15957713', + 'sites': [], + 'tags': ['最满意的风光照'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15957713/61839750/', + 'views': 645 + }, + { + 'author_id': '15948532', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '枝繁,叶茂', + 'created_at': '2020-01-19 10:53:34', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['极简主义,少即是多'], + 'excerpt': '枝繁,叶茂', + 'favorite_list_prefix': [], + 'favorites': 11, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1184, + 'img_id': 247383812, + 'img_id_str': '247383812', + 'title': '001', + 'user_id': 15948532, + 'width': 1776 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月19日', + 'post_id': 61833716, + 'published_at': '2020-01-19 10:53:34', + 'recommend': true, + 'recom_type': '', + 'rewardable': false, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '一个脱离了高级趣味的人', + 'domain': '', + 'followers': 14, + 'has_everphoto_note': true, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15948532_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '子跳儿', + 'site_id': '15948532', + 'type': 'user', + 'url': 'https://tuchong.com/15948532/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15948532', + 'sites': [], + 'tags': ['极简主义,少即是多', '静物'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15948532/61833716/', + 'views': 574 + }, + { + 'author_id': '15172887', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 5, + 'content': '', + 'created_at': '2020-01-18 21:42:58', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['河北摄影圈'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 26, + 'image_count': 4, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3002, + 'img_id': 138331852, + 'img_id_str': '138331852', + 'title': '16673', + 'user_id': 15172887, + 'width': 4503 + }, + { + 'description': '', + 'excerpt': '', + 'height': 3087, + 'img_id': 594134774, + 'img_id_str': '594134774', + 'title': '16672', + 'user_id': 15172887, + 'width': 4630 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2848, + 'img_id': 163170258, + 'img_id_str': '163170258', + 'title': '16674', + 'user_id': 15172887, + 'width': 4272 + }, + { + 'description': '', + 'excerpt': '', + 'height': 799, + 'img_id': 640403303, + 'img_id_str': '640403303', + 'title': '16675', + 'user_id': 15172887, + 'width': 571 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '5', + 'passed_time': '01月18日', + 'post_id': 61822228, + 'published_at': '2020-01-18 21:42:58', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '河北石家庄(东方森林)', + 'domain': '', + 'followers': 153, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15172887_1', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '东方森林1', + 'site_id': '15172887', + 'type': 'user', + 'url': 'https://tuchong.com/15172887/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15172887', + 'sites': [], + 'tags': ['河北摄影圈'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15172887/61822228/', + 'views': 1069 + }, + { + 'author_id': '1016626', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 6, + 'content': '我在唐古拉山,你呢?(唐古拉山口,此时海拨5000米以上。摄于2016年夏。)', + 'created_at': '2020-01-18 20:28:01', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '南京航空航天大学摄影', + '福建摄影圈', + '极简主义,少即是多', + '驚鴻·一眸', + '火烛一花精品摄影', + '南京大学摄影圈', + '晒晒旅行打卡照', + '行摄部落', + '人像爱好者', + '河北摄影圈' + ], + 'excerpt': '我在唐古拉山,你呢?(唐古拉山口,此时海拨5000米以上。摄于2016年夏。)', + 'favorite_list_prefix': [], + 'favorites': 32, + 'image_count': 2, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 5760, + 'img_id': 182175906, + 'img_id_str': '182175906', + 'title': '001', + 'user_id': 1016626, + 'width': 3840 + }, + { + 'description': '', + 'excerpt': '', + 'height': 5532, + 'img_id': 35243734, + 'img_id_str': '35243734', + 'title': '001', + 'user_id': 1016626, + 'width': 3681 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '3', + 'passed_time': '01月18日', + 'post_id': 61820096, + 'published_at': '2020-01-18 20:28:01', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '不是在路上,就是准备去远方。', + 'domain': '', + 'followers': 47, + 'has_everphoto_note': false, + 'icon': + 'https://lf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_1016626_4', + 'is_bind_everphoto': true, + 'is_following': false, + 'name': '杨乐乐哥', + 'site_id': '1016626', + 'type': 'user', + 'url': 'https://tuchong.com/1016626/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '1016626', + 'sites': [], + 'tags': [ + '南京航空航天大学摄影', + '福建摄影圈', + '极简主义,少即是多', + '驚鴻·一眸', + '火烛一花精品摄影', + '南京大学摄影圈' + ], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/1016626/61820096/', + 'views': 1301 + }, + { + 'author_id': '15946105', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 1, + 'content': '适合郊游的好天气', + 'created_at': '2020-01-18 18:15:24', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['我们都爱日系摄影', '生活小确幸', '云上的日子'], + 'excerpt': '适合郊游的好天气', + 'favorite_list_prefix': [], + 'favorites': 15, + 'image_count': 4, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 4160, + 'img_id': 489670452, + 'img_id_str': '489670452', + 'title': '2155899', + 'user_id': 15946105, + 'width': 6240 + }, + { + 'description': '', + 'excerpt': '', + 'height': 6240, + 'img_id': 95798826, + 'img_id_str': '95798826', + 'title': '2155902', + 'user_id': 15946105, + 'width': 4160 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2976, + 'img_id': 565036511, + 'img_id_str': '565036511', + 'title': '2227302', + 'user_id': 15946105, + 'width': 3968 + }, + { + 'description': '', + 'excerpt': '', + 'height': 2976, + 'img_id': 472302966, + 'img_id_str': '472302966', + 'title': '2227300', + 'user_id': 15946105, + 'width': 3968 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月18日', + 'post_id': 61816866, + 'published_at': '2020-01-18 18:15:24', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': 'wb:-青-森', + 'domain': '', + 'followers': 9, + 'has_everphoto_note': true, + 'icon': + 'https://sf3-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15946105_2', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '青森-', + 'site_id': '15946105', + 'type': 'user', + 'url': 'https://tuchong.com/15946105/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15946105', + 'sites': [], + 'tags': ['我们都爱日系摄影', '生活小确幸', '云上的日子', '季节', '公园', '树'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15946105/61816866/', + 'views': 597 + }, + { + 'author_id': '15952461', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 2, + 'content': '', + 'created_at': '2020-01-18 16:55:13', + 'data_type': 'post', + 'delete': false, + 'event_tags': [ + '浙江摄影俱乐部', + '华为摄影爱好者', + '图虫旅行摄影圈子', + '风光摄影圈', + '生活小确幸', + '最满意的风光照', + '杭州风光圈子' + ], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 19, + 'image_count': 1, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 3648, + 'img_id': 63686594, + 'img_id_str': '63686594', + 'title': '896574', + 'user_id': 15952461, + 'width': 2736 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '1', + 'passed_time': '01月18日', + 'post_id': 61815151, + 'published_at': '2020-01-18 16:55:13', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '0', + 'rqt_id': '', + 'shares': 0, + 'site': { + 'description': '', + 'domain': '', + 'followers': 9, + 'has_everphoto_note': true, + 'icon': + 'https://lf1-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_15952461_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '我可是邓紫棋的宝贝呢', + 'site_id': '15952461', + 'type': 'user', + 'url': 'https://tuchong.com/15952461/', + 'verification_list': [], + 'verifications': 0, + 'verified': false + }, + 'site_id': '15952461', + 'sites': [], + 'tags': ['浙江摄影俱乐部', '华为摄影爱好者', '图虫旅行摄影圈子', '风光摄影圈', '生活小确幸', '最满意的风光照'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/15952461/61815151/', + 'views': 778 + }, + { + 'author_id': '7381082', + 'collected': false, + 'comment_list_prefix': [], + 'comments': 44, + 'content': '', + 'created_at': '2019-08-23 09:31:48', + 'data_type': 'post', + 'delete': false, + 'event_tags': ['度假就要去海滩'], + 'excerpt': '', + 'favorite_list_prefix': [], + 'favorites': 958, + 'image_count': 10, + 'images': [ + { + 'description': '', + 'excerpt': '', + 'height': 1611, + 'img_id': 349476280, + 'img_id_str': '349476280', + 'title': '726910', + 'user_id': 7381082, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1617, + 'img_id': 192386263, + 'img_id_str': '192386263', + 'title': '726920', + 'user_id': 7381082, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1603, + 'img_id': 166762337, + 'img_id_str': '166762337', + 'title': '726921', + 'user_id': 7381082, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1598, + 'img_id': 565483041, + 'img_id_str': '565483041', + 'title': '726922', + 'user_id': 7381082, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1614, + 'img_id': 605656784, + 'img_id_str': '605656784', + 'title': '726923', + 'user_id': 7381082, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 708, + 'img_id': 443651882, + 'img_id_str': '443651882', + 'title': '726926', + 'user_id': 7381082, + 'width': 1063 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1599, + 'img_id': 121542087, + 'img_id_str': '121542087', + 'title': '726928', + 'user_id': 7381082, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1613, + 'img_id': 231904857, + 'img_id_str': '231904857', + 'title': '726925', + 'user_id': 7381082, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 703, + 'img_id': 545625928, + 'img_id_str': '545625928', + 'title': '726924', + 'user_id': 7381082, + 'width': 1080 + }, + { + 'description': '', + 'excerpt': '', + 'height': 1608, + 'img_id': 394696269, + 'img_id_str': '394696269', + 'title': '726927', + 'user_id': 7381082, + 'width': 1080 + } + ], + 'is_favorite': false, + 'last_read': null, + 'parent_comments': '40', + 'passed_time': '2019年08月23日', + 'post_id': 50291665, + 'published_at': '2019-08-23 09:31:48', + 'recommend': true, + 'recom_type': '', + 'rewardable': true, + 'reward_list_prefix': [], + 'rewards': '1', + 'rqt_id': '', + 'shares': 24, + 'site': { + 'description': '资深人像摄影师', + 'domain': '', + 'followers': 1885, + 'has_everphoto_note': true, + 'icon': + 'https://sf6-tccdn-tos.pstatp.com/obj/tuchong-avatar/ll_7381082_1', + 'is_bind_everphoto': false, + 'is_following': false, + 'name': '原来是姜雯呀', + 'site_id': '7381082', + 'type': 'user', + 'url': 'https://tuchong.com/7381082/', + 'verification_list': [ + {'verification_reason': '资深人像摄影师', 'verification_type': 13} + ], + 'verifications': 1, + 'verified': true + }, + 'site_id': '7381082', + 'sites': [], + 'tags': ['度假就要去海滩', '瓦特·清新滤镜'], + 'title': '', + 'title_image': null, + 'type': 'multi-photo', + 'update': false, + 'url': 'https://tuchong.com/7381082/50291665/', + 'views': 54356 + } + ], + 'is_history': false, + 'message': '添加成功', + 'more': true, + 'result': 'SUCCESS' +}; + +TuChongSource _mockSource = TuChongSource.fromJson(_mock); +TuChongSource get mockSource => _mockSource; diff --git a/local_packages/extended_text_field-16.0.2/example/lib/data/tu_chong_repository.dart b/local_packages/extended_text_field-16.0.2/example/lib/data/tu_chong_repository.dart new file mode 100644 index 0000000..af50cd2 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/data/tu_chong_repository.dart @@ -0,0 +1,91 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:http_client_helper/http_client_helper.dart'; +import 'package:loading_more_list_library/loading_more_list_library.dart'; + +import 'mock_data.dart'; +import 'tu_chong_source.dart'; + +Future onLikeButtonTap(bool isLiked, TuChongItem item) { + ///send your request here + return Future.delayed(const Duration(milliseconds: 50), () { + item.isFavorite = !item.isFavorite!; + item.favorites = + item.isFavorite! ? item.favorites! + 1 : item.favorites! - 1; + return item.isFavorite!; + }); +} + +class TuChongRepository extends LoadingMoreBase { + TuChongRepository({this.maxLength = 300}); + int _pageIndex = 1; + bool _hasMore = true; + @override + bool get hasMore => _hasMore && length < maxLength; + final int maxLength; + + @override + Future refresh([bool notifyStateChanged = false]) async { + _hasMore = true; + _pageIndex = 1; + + final bool result = await super.refresh(true); + return result; + } + + @override + Future loadData([bool isLoadMoreAction = false]) async { + String url = ''; + if (isEmpty) { + url = 'https://api.tuchong.com/feed-app'; + } else { + final int? lastPostId = this[length - 1].postId; + url = + 'https://api.tuchong.com/feed-app?post_id=$lastPostId&page=$_pageIndex&type=loadmore'; + } + bool isSuccess = false; + try { + //to show loading more clearly, in your app,remove this + // await Future.delayed(const Duration(seconds: 2)); + List? feedList; + if (!kIsWeb) { + final Response result = + await HttpClientHelper.get(Uri.parse(url)) as Response; + feedList = TuChongSource.fromJson( + json.decode(result.body) as Map) + .feedList; + } else { + feedList = mockSource.feedList!.getRange(length, length + 20).toList(); + } + + if (_pageIndex == 1) { + clear(); + } + + for (final TuChongItem item in feedList!) { + if (item.hasImage && !contains(item) && hasMore) { + add(item); + } + } + + _hasMore = feedList.isNotEmpty; + _pageIndex++; + isSuccess = true; + } catch (exception, stack) { + isSuccess = false; + if (kDebugMode) { + print(exception); + + print(stack); + } + } + return isSuccess; + } + + // @override + // Iterable wrapData(Iterable source) { + // return source.where((TuChongItem element) => element.imageCount == 1); + // } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/data/tu_chong_source.dart b/local_packages/extended_text_field-16.0.2/example/lib/data/tu_chong_source.dart new file mode 100644 index 0000000..bde9c2e --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/data/tu_chong_source.dart @@ -0,0 +1,559 @@ +import 'dart:convert' show json; +import 'dart:math'; +import 'package:flutter/rendering.dart'; + +void tryCatch(Function f) { + try { + f.call(); + } catch (e, stack) { + debugPrint('$e'); + debugPrint('$stack'); + } +} + +T? asT(dynamic value) { + if (value is T) { + return value; + } + if (value != null) { + final String valueS = value.toString(); + if (0 is T) { + return int.tryParse(valueS) as T?; + } else if (0.0 is T) { + return double.tryParse(valueS) as T?; + } else if ('' is T) { + return valueS as T; + } else if (false is T) { + if (valueS == '0' || valueS == '1') { + return (valueS == '1') as T; + } + return (valueS == 'true') as T; + } + } + return null; +} + +class TuChongSource { + TuChongSource({ + this.counts, + this.feedList, + this.isHistory, + this.message, + this.more, + this.result, + }); + + factory TuChongSource.fromJson(Map jsonRes) { + final List? feedList = + jsonRes['feedList'] is List ? [] : null; + if (feedList != null) { + for (final dynamic item in jsonRes['feedList']) { + if (item != null) { + tryCatch(() { + feedList + .add(TuChongItem.fromJson(asT>(item)!)); + }); + } + } + } + + return TuChongSource( + counts: asT(jsonRes['counts']), + feedList: feedList, + isHistory: asT(jsonRes['is_history']), + message: asT(jsonRes['message']), + more: asT(jsonRes['more']), + result: asT(jsonRes['result']), + ); + } + + final int? counts; + final List? feedList; + final bool? isHistory; + final String? message; + final bool? more; + final String? result; + + Map toJson() => { + 'counts': counts, + 'feedList': feedList, + 'is_history': isHistory, + 'message': message, + 'more': more, + 'result': result, + }; + + @override + String toString() { + return json.encode(this); + } +} + +class TuChongItem { + TuChongItem({ + this.authorId, + this.collected, + this.commentListPrefix, + this.comments, + this.content, + this.createdAt, + this.dataType, + this.delete, + this.eventTags, + this.excerpt, + this.favoriteListPrefix, + this.favorites, + this.imageCount, + this.images, + this.isFavorite, + this.parentComments, + this.passedTime, + this.postId, + this.publishedAt, + this.recommend, + this.recomType, + this.rewardable, + this.rewardListPrefix, + this.rewards, + this.rqtId, + this.shares, + this.site, + this.siteId, + this.sites, + this.tags, + this.title, + this.titleImage, + this.type, + this.update, + this.url, + this.views, + this.tagColors, + }); + + factory TuChongItem.fromJson(Map jsonRes) { + final List? commentListPrefix = + jsonRes['comment_list_prefix'] is List ? [] : null; + if (commentListPrefix != null) { + for (final dynamic item in jsonRes['comment_list_prefix']) { + if (item != null) { + tryCatch(() { + commentListPrefix.add(asT(item)); + }); + } + } + } + + final List? eventTags = + jsonRes['event_tags'] is List ? [] : null; + if (eventTags != null) { + for (final dynamic item in jsonRes['event_tags']) { + if (item != null) { + tryCatch(() { + eventTags.add(asT(item)); + }); + } + } + } + + final List? favoriteListPrefix = + jsonRes['favorite_list_prefix'] is List ? [] : null; + if (favoriteListPrefix != null) { + for (final dynamic item in jsonRes['favorite_list_prefix']) { + if (item != null) { + tryCatch(() { + favoriteListPrefix.add(asT(item)); + }); + } + } + } + + final List? images = + jsonRes['images'] is List ? [] : null; + if (images != null) { + for (final dynamic item in jsonRes['images']) { + if (item != null) { + tryCatch(() { + images.add(ImageItem.fromJson(asT>(item)!)); + }); + } + } + } + + final List? rewardListPrefix = + jsonRes['reward_list_prefix'] is List ? [] : null; + if (rewardListPrefix != null) { + for (final dynamic item in jsonRes['reward_list_prefix']) { + if (item != null) { + tryCatch(() { + rewardListPrefix.add(asT(item)); + }); + } + } + } + + final List? sites = jsonRes['sites'] is List ? [] : null; + if (sites != null) { + for (final dynamic item in jsonRes['sites']) { + if (item != null) { + tryCatch(() { + sites.add(asT(item)); + }); + } + } + } + final List tagColors = []; + final List? tags = jsonRes['tags'] is List ? [] : null; + if (tags != null) { + const int maxNum = 6; + for (final dynamic item in jsonRes['tags']) { + if (item != null) { + tryCatch(() { + tags.add(asT(item)); + tagColors.add(Color.fromARGB(255, Random.secure().nextInt(255), + Random.secure().nextInt(255), Random.secure().nextInt(255))); + }); + } + if (tags.length == maxNum) { + break; + } + } + } + + return TuChongItem( + authorId: asT(jsonRes['author_id']), + collected: asT(jsonRes['collected']), + commentListPrefix: commentListPrefix, + comments: asT(jsonRes['comments']), + content: asT(jsonRes['content']), + createdAt: asT(jsonRes['created_at']), + dataType: asT(jsonRes['data_type']), + delete: asT(jsonRes['delete']), + eventTags: eventTags, + excerpt: asT(jsonRes['excerpt']), + favoriteListPrefix: favoriteListPrefix, + favorites: asT(jsonRes['favorites']), + imageCount: asT(jsonRes['image_count']), + images: images, + isFavorite: asT(jsonRes['is_favorite']), + parentComments: asT(jsonRes['parent_comments']), + passedTime: asT(jsonRes['passed_time']), + postId: asT(jsonRes['post_id']), + publishedAt: asT(jsonRes['published_at']), + recommend: asT(jsonRes['recommend']), + recomType: asT(jsonRes['recom_type']), + rewardable: asT(jsonRes['rewardable']), + rewardListPrefix: rewardListPrefix, + rewards: asT(jsonRes['rewards']), + rqtId: asT(jsonRes['rqt_id']), + shares: asT(jsonRes['shares']), + site: Site.fromJson(asT>(jsonRes['site'])!), + siteId: asT(jsonRes['site_id']), + sites: sites, + tags: tags, + tagColors: tagColors, + title: asT(jsonRes['title']), + titleImage: asT(jsonRes['title_image']), + type: asT(jsonRes['type']), + update: asT(jsonRes['update']), + url: asT(jsonRes['url']), + views: asT(jsonRes['views']), + ); + } + + final String? authorId; + final bool? collected; + final List? commentListPrefix; + final int? comments; + final String? content; + final String? createdAt; + final String? dataType; + final bool? delete; + final List? eventTags; + final String? excerpt; + final List? favoriteListPrefix; + int? favorites; + final int? imageCount; + final List? images; + bool? isFavorite; + final String? parentComments; + final String? passedTime; + final int? postId; + final String? publishedAt; + final bool? recommend; + final String? recomType; + final bool? rewardable; + final List? rewardListPrefix; + final String? rewards; + final String? rqtId; + final int? shares; + final Site? site; + final String? siteId; + final List? sites; + final List? tags; + final String? title; + final Object? titleImage; + final String? type; + final bool? update; + final String? url; + final int? views; + final List? tagColors; + bool get hasImage { + return images != null && images!.isNotEmpty; + } + + Size? imageRawSize; + + Size get imageSize { + if (!hasImage) { + return const Size(0, 0); + } + return Size(images![0].width!.toDouble(), images![0].height!.toDouble()); + } + + String get imageUrl { + if (!hasImage) { + return ''; + } + return 'https://photo.tuchong.com/${images![0].userId}/f/${images![0].imgId}.jpg'; + } + + String? get avatarUrl => site!.icon; + + String? get imageTitle { + if (!hasImage) { + return title; + } + + return images![0].title; + } + + String? get imageDescription { + if (!hasImage) { + return content; + } + + return images![0].description; + } + + Map toJson() => { + 'author_id': authorId, + 'collected': collected, + 'comment_list_prefix': commentListPrefix, + 'comments': comments, + 'content': content, + 'created_at': createdAt, + 'data_type': dataType, + 'delete': delete, + 'event_tags': eventTags, + 'excerpt': excerpt, + 'favorite_list_prefix': favoriteListPrefix, + 'favorites': favorites, + 'image_count': imageCount, + 'images': images, + 'is_favorite': isFavorite, + 'parent_comments': parentComments, + 'passed_time': passedTime, + 'post_id': postId, + 'published_at': publishedAt, + 'recommend': recommend, + 'recom_type': recomType, + 'rewardable': rewardable, + 'reward_list_prefix': rewardListPrefix, + 'rewards': rewards, + 'rqt_id': rqtId, + 'shares': shares, + 'site': site, + 'site_id': siteId, + 'sites': sites, + 'tags': tags, + 'title': title, + 'title_image': titleImage, + 'type': type, + 'update': update, + 'url': url, + 'views': views, + }; + + @override + String toString() { + return json.encode(this); + } +} + +class ImageItem { + ImageItem({ + this.description, + this.excerpt, + this.height, + this.imgId, + this.imgIdStr, + this.title, + this.userId, + this.width, + }); + + factory ImageItem.fromJson(Map jsonRes) => ImageItem( + description: asT(jsonRes['description']), + excerpt: asT(jsonRes['excerpt']), + height: asT(jsonRes['height']), + imgId: asT(jsonRes['img_id']), + imgIdStr: asT(jsonRes['img_id_str']), + title: asT(jsonRes['title']), + userId: asT(jsonRes['user_id']), + width: asT(jsonRes['width']), + ); + + final String? description; + final String? excerpt; + final int? height; + final int? imgId; + final String? imgIdStr; + final String? title; + final int? userId; + final int? width; + String get imageUrl { + return 'https://photo.tuchong.com/$userId/f/$imgId.jpg'; + } + // ImageProvider createNetworkImage() { + // return ExtendedNetworkImageProvider(imageUrl); + // } + + // ImageProvider createResizeImage() { + // return ResizeImage(ExtendedNetworkImageProvider(imageUrl), + // width: width ~/ 5, height: height ~/ 5); + // } + + // void clearCache() { + // createNetworkImage().evict(); + // createResizeImage().evict(); + // } + Map toJson() => { + 'description': description, + 'excerpt': excerpt, + 'height': height, + 'img_id': imgId, + 'img_id_str': imgIdStr, + 'title': title, + 'user_id': userId, + 'width': width, + }; + + @override + String toString() { + return json.encode(this); + } +} + +class Site { + Site({ + this.description, + this.domain, + this.followers, + this.hasEverphotoNote, + this.icon, + this.isBindEverphoto, + this.isFollowing, + this.name, + this.siteId, + this.type, + this.url, + this.verificationList, + this.verifications, + this.verified, + }); + + factory Site.fromJson(Map jsonRes) { + final List? verificationList = + jsonRes['verification_list'] is List ? [] : null; + if (verificationList != null) { + for (final dynamic item in jsonRes['verification_list']) { + if (item != null) { + tryCatch(() { + verificationList.add( + VerificationList.fromJson(asT>(item)!)); + }); + } + } + } + + return Site( + description: asT(jsonRes['description']), + domain: asT(jsonRes['domain']), + followers: asT(jsonRes['followers']), + hasEverphotoNote: asT(jsonRes['has_everphoto_note']), + icon: asT(jsonRes['icon']), + isBindEverphoto: asT(jsonRes['is_bind_everphoto']), + isFollowing: asT(jsonRes['is_following']), + name: asT(jsonRes['name']), + siteId: asT(jsonRes['site_id']), + type: asT(jsonRes['type']), + url: asT(jsonRes['url']), + verificationList: verificationList, + verifications: asT(jsonRes['verifications']), + verified: asT(jsonRes['verified']), + ); + } + + final String? description; + final String? domain; + int? followers; + final bool? hasEverphotoNote; + final String? icon; + final bool? isBindEverphoto; + bool? isFollowing; + final String? name; + final String? siteId; + final String? type; + final String? url; + final List? verificationList; + final int? verifications; + final bool? verified; + + Map toJson() => { + 'description': description, + 'domain': domain, + 'followers': followers, + 'has_everphoto_note': hasEverphotoNote, + 'icon': icon, + 'is_bind_everphoto': isBindEverphoto, + 'is_following': isFollowing, + 'name': name, + 'site_id': siteId, + 'type': type, + 'url': url, + 'verification_list': verificationList, + 'verifications': verifications, + 'verified': verified, + }; + + @override + String toString() { + return json.encode(this); + } +} + +class VerificationList { + VerificationList({ + this.verificationReason, + this.verificationType, + }); + + factory VerificationList.fromJson(Map jsonRes) => + VerificationList( + verificationReason: asT(jsonRes['verification_reason']), + verificationType: asT(jsonRes['verification_type']), + ); + + final String? verificationReason; + final int? verificationType; + + Map toJson() => { + 'verification_reason': verificationReason, + 'verification_type': verificationType, + }; + + @override + String toString() { + return json.encode(this); + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/example_route.dart b/local_packages/extended_text_field-16.0.2/example/lib/example_route.dart new file mode 100644 index 0000000..5abbbd5 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/example_route.dart @@ -0,0 +1,118 @@ +// GENERATED CODE - DO NOT MODIFY MANUALLY +// ************************************************************************** +// Auto generated by https://github.com/fluttercandies/ff_annotation_route +// ************************************************************************** +// fast mode: true +// version: 10.0.9 +// ************************************************************************** +// ignore_for_file: prefer_const_literals_to_create_immutables,unused_local_variable,unused_import,unnecessary_import,unused_shown_name,implementation_imports,duplicate_import +import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; +import 'package:flutter/widgets.dart'; + +import 'pages/complex/text_demo.dart'; +import 'pages/main_page.dart'; +import 'pages/simple/custom_toolbar.dart'; +import 'pages/simple/no_keyboard.dart'; +import 'pages/simple/selectable_text.dart'; +import 'pages/simple/widget_span.dart'; + +/// Get route settings base on route name, auto generated by https://github.com/fluttercandies/ff_annotation_route +FFRouteSettings getRouteSettings({ + required String name, + Map? arguments, + PageBuilder? notFoundPageBuilder, +}) { + final Map safeArguments = + arguments ?? const {}; + switch (name) { + case 'fluttercandies://CustomToolBar': + return FFRouteSettings( + name: name, + arguments: arguments, + builder: () => CustomToolBar(), + routeName: 'custom toolbar', + description: 'custom selection toolbar and handles for text field', + exts: { + 'group': 'Simple', + 'order': 0, + }, + ); + case 'fluttercandies://NoKeyboard': + return FFRouteSettings( + name: name, + arguments: arguments, + builder: () => NoSystemKeyboardDemo( + key: asT( + safeArguments['key'], + ), + ), + routeName: 'no system Keyboard', + description: + 'show how to ignore system keyboard and show custom keyboard', + exts: { + 'group': 'Simple', + 'order': 2, + }, + ); + case 'fluttercandies://SelectableTextDemo': + return FFRouteSettings( + name: name, + arguments: arguments, + builder: () => SelectableTextDemo(), + routeName: 'SelectableText', + description: 'support SelectableText', + exts: { + 'group': 'Simple', + 'order': 3, + }, + ); + case 'fluttercandies://TextDemo': + return FFRouteSettings( + name: name, + arguments: arguments, + builder: () => TextDemo(), + routeName: 'text', + description: 'build special text and inline image in text field', + exts: { + 'group': 'Complex', + 'order': 0, + }, + ); + case 'fluttercandies://WidgetSpanDemo': + return FFRouteSettings( + name: name, + arguments: arguments, + builder: () => WidgetSpanDemo(), + routeName: 'widget span', + description: 'mailbox demo with widgetSpan', + exts: { + 'group': 'Simple', + 'order': 1, + }, + ); + case 'fluttercandies://demogrouppage': + return FFRouteSettings( + name: name, + arguments: arguments, + builder: () => DemoGroupPage( + keyValue: asT>>( + safeArguments['keyValue'], + )!, + ), + routeName: 'DemoGroupPage', + ); + case 'fluttercandies://mainpage': + return FFRouteSettings( + name: name, + arguments: arguments, + builder: () => MainPage(), + routeName: 'MainPage', + ); + default: + return FFRouteSettings( + name: FFRoute.notFoundName, + routeName: FFRoute.notFoundRouteName, + builder: notFoundPageBuilder ?? () => Container(), + ); + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/example_routes.dart b/local_packages/extended_text_field-16.0.2/example/lib/example_routes.dart new file mode 100644 index 0000000..36a04db --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/example_routes.dart @@ -0,0 +1,108 @@ +// GENERATED CODE - DO NOT MODIFY MANUALLY +// ************************************************************************** +// Auto generated by https://github.com/fluttercandies/ff_annotation_route +// ************************************************************************** +// fast mode: true +// version: 10.0.9 +// ************************************************************************** +// ignore_for_file: prefer_const_literals_to_create_immutables,unused_local_variable,unused_import,unnecessary_import,unused_shown_name,implementation_imports,duplicate_import +/// The routeNames auto generated by https://github.com/fluttercandies/ff_annotation_route +const List routeNames = [ + 'fluttercandies://CustomToolBar', + 'fluttercandies://NoKeyboard', + 'fluttercandies://SelectableTextDemo', + 'fluttercandies://TextDemo', + 'fluttercandies://WidgetSpanDemo', + 'fluttercandies://demogrouppage', + 'fluttercandies://mainpage', +]; + +/// The routes auto generated by https://github.com/fluttercandies/ff_annotation_route +class Routes { + const Routes._(); + + /// 'custom selection toolbar and handles for text field' + /// + /// [name] : 'fluttercandies://CustomToolBar' + /// + /// [routeName] : 'custom toolbar' + /// + /// [description] : 'custom selection toolbar and handles for text field' + /// + /// [exts] : {'group': 'Simple', 'order': 0} + static const String fluttercandiesCustomToolBar = + 'fluttercandies://CustomToolBar'; + + /// 'show how to ignore system keyboard and show custom keyboard' + /// + /// [name] : 'fluttercandies://NoKeyboard' + /// + /// [routeName] : 'no system Keyboard' + /// + /// [description] : 'show how to ignore system keyboard and show custom keyboard' + /// + /// [constructors] : + /// + /// NoSystemKeyboardDemo : [Key? key] + /// + /// [exts] : {'group': 'Simple', 'order': 2} + static const String fluttercandiesNoKeyboard = 'fluttercandies://NoKeyboard'; + + /// 'support SelectableText' + /// + /// [name] : 'fluttercandies://SelectableTextDemo' + /// + /// [routeName] : 'SelectableText' + /// + /// [description] : 'support SelectableText' + /// + /// [exts] : {'group': 'Simple', 'order': 3} + static const String fluttercandiesSelectableTextDemo = + 'fluttercandies://SelectableTextDemo'; + + /// 'build special text and inline image in text field' + /// + /// [name] : 'fluttercandies://TextDemo' + /// + /// [routeName] : 'text' + /// + /// [description] : 'build special text and inline image in text field' + /// + /// [exts] : {'group': 'Complex', 'order': 0} + static const String fluttercandiesTextDemo = 'fluttercandies://TextDemo'; + + /// 'mailbox demo with widgetSpan' + /// + /// [name] : 'fluttercandies://WidgetSpanDemo' + /// + /// [routeName] : 'widget span' + /// + /// [description] : 'mailbox demo with widgetSpan' + /// + /// [exts] : {'group': 'Simple', 'order': 1} + static const String fluttercandiesWidgetSpanDemo = + 'fluttercandies://WidgetSpanDemo'; + + /// 'DemoGroupPage' + /// + /// [name] : 'fluttercandies://demogrouppage' + /// + /// [routeName] : 'DemoGroupPage' + /// + /// [constructors] : + /// + /// DemoGroupPage : [MapEntry>(required) keyValue] + static const String fluttercandiesDemogrouppage = + 'fluttercandies://demogrouppage'; + + /// 'MainPage' + /// + /// [name] : 'fluttercandies://mainpage' + /// + /// [routeName] : 'MainPage' + /// + /// [constructors] : + /// + /// MainPage : [] + static const String fluttercandiesMainpage = 'fluttercandies://mainpage'; +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/generated_plugin_registrant.dart b/local_packages/extended_text_field-16.0.2/example/lib/generated_plugin_registrant.dart new file mode 100644 index 0000000..40a0d00 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/generated_plugin_registrant.dart @@ -0,0 +1,17 @@ +// +// Generated file. Do not edit. +// + +// ignore_for_file: directives_ordering +// ignore_for_file: lines_longer_than_80_chars +// ignore_for_file: depend_on_referenced_packages + +import 'package:url_launcher_web/url_launcher_web.dart'; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +// ignore: public_member_api_docs +void registerPlugins(Registrar registrar) { + UrlLauncherPlugin.registerWith(registrar); + registrar.registerMessageHandler(); +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/main.dart b/local_packages/extended_text_field-16.0.2/example/lib/main.dart new file mode 100644 index 0000000..da0258f --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/main.dart @@ -0,0 +1,38 @@ +import 'package:example/pages/simple/no_keyboard.dart'; +import 'package:extended_keyboard/extended_keyboard.dart'; +import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; +import 'package:flutter/material.dart'; +import 'package:oktoast/oktoast.dart'; +import 'example_route.dart'; +import 'example_routes.dart'; + +Future main() async { + CustomKeyboarBinding(); + await SystemKeyboard().init(); + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + //EditableText + //TextField + return OKToast( + child: MaterialApp( + title: 'extended_text_field demo', + debugShowCheckedModeBanner: false, + theme: ThemeData( + primarySwatch: Colors.blue, + ), + initialRoute: Routes.fluttercandiesMainpage, + onGenerateRoute: (RouteSettings settings) { + return onGenerateRoute( + settings: settings, + getRouteSettings: getRouteSettings, + ); + }, + ), + ); + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/pages/complex/text_demo.dart b/local_packages/extended_text_field-16.0.2/example/lib/pages/complex/text_demo.dart new file mode 100644 index 0000000..fa2a720 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/pages/complex/text_demo.dart @@ -0,0 +1,424 @@ +import 'package:example/common/toggle_button.dart'; +import 'package:example/data/tu_chong_repository.dart'; +import 'package:example/data/tu_chong_source.dart'; +import 'package:example/special_text/at_text.dart'; +import 'package:example/special_text/dollar_text.dart'; +import 'package:example/special_text/emoji_text.dart' as emoji; +import 'package:example/special_text/my_extended_text_selection_controls.dart'; +import 'package:example/special_text/my_special_text_span_builder.dart'; +import 'package:extended_image/extended_image.dart'; +import 'package:extended_keyboard/extended_keyboard.dart'; +import 'package:extended_list/extended_list.dart'; +import 'package:extended_text/extended_text.dart'; +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:loading_more_list/loading_more_list.dart'; +import 'package:url_launcher/url_launcher.dart'; + +enum KeyboardPanelType { + system, + emoji, + image, + at, + dollar, +} + +@FFRoute( + name: 'fluttercandies://TextDemo', + routeName: 'text', + description: 'build special text and inline image in text field', + exts: { + 'group': 'Complex', + 'order': 0, + }, +) +class TextDemo extends StatefulWidget { + @override + _TextDemoState createState() => _TextDemoState(); +} + +class _TextDemoState extends State { + KeyboardPanelType _keyboardPanelType = KeyboardPanelType.system; + final TextEditingController _textEditingController = TextEditingController(); + final MyTextSelectionControls _myExtendedMaterialTextSelectionControls = + MyTextSelectionControls(); + final GlobalKey _key = + GlobalKey(); + final MySpecialTextSpanBuilder _mySpecialTextSpanBuilder = + MySpecialTextSpanBuilder(); + late TuChongRepository imageList = TuChongRepository(maxLength: 100); + final FocusNode _focusNode = FocusNode(); + + List sessions = [ + '[44] @Dota2 CN dota best dota', + 'yes, you are right [36].', + '大家好,我是拉面,很萌很新 [12].', + '\$Flutter\$. CN dev best dev', + '\$Dota2 Ti9\$. Shanghai,I\'m coming.', + 'error 0 [45] warning 0', + ]; + + Duration duration = const Duration(milliseconds: 300); + + final ScrollController _controller = ScrollController(); + + final CustomKeyboardController _customKeyboardController = + CustomKeyboardController(KeyboardType.system); + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: const Text('special text'), + actions: [ + TextButton( + child: const Icon( + Icons.backspace, + color: Colors.white, + ), + onPressed: manualDelete, + ) + ], + ), + body: SafeArea( + bottom: true, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + _customKeyboardController.unfocus(); + }, + child: KeyboardBuilder( + controller: _customKeyboardController, + resizeToAvoidBottomInset: true, + bodyBuilder: (bool readOnly) => Column( + children: [ + Expanded( + child: ExtendedListView.builder( + extendedListDelegate: const ExtendedListDelegate( + closeToTrailing: true, + ), + controller: _controller, + itemBuilder: (BuildContext context, int index) { + final bool left = index % 2 == 0; + final Image logo = Image.asset( + 'assets/flutter_candies_logo.png', + width: 30.0, + height: 30.0, + ); + //print(sessions[index]); + final Widget text = ExtendedText( + sessions[index], + textAlign: left ? TextAlign.left : TextAlign.right, + specialTextSpanBuilder: _mySpecialTextSpanBuilder, + onSpecialTextTap: (dynamic value) { + if (value.toString().startsWith('\$')) { + launchUrl( + Uri.parse('https://github.com/fluttercandies')); + } else if (value.toString().startsWith('@')) { + launchUrl(Uri.parse('mailto:zmtzawqlp@live.com')); + } + }, + ); + List list = [ + logo, + Expanded(child: text), + Container( + width: 30.0, + ) + ]; + if (!left) { + list = list.reversed.toList(); + } + return Row( + children: list, + ); + }, + padding: const EdgeInsets.only(bottom: 10.0), + reverse: true, + itemCount: sessions.length, + )), + Container( + padding: const EdgeInsets.all(5), + decoration: const BoxDecoration( + border: Border( + top: BorderSide( + color: Colors.blue, + ), + bottom: BorderSide( + color: Colors.blue, + ), + ), + ), + child: Column( + children: [ + ExtendedTextField( + key: _key, + minLines: 1, + maxLines: 2, + showCursor: true, + readOnly: readOnly, + + // StrutStyle get strutStyle { + // if (_strutStyle == null) { + // return StrutStyle.fromTextStyle(style, forceStrutHeight: true); + // } + // return _strutStyle!.inheritFromTextStyle(style); + // } + // default strutStyle is not good for WidgetSpan + strutStyle: const StrutStyle(), + specialTextSpanBuilder: MySpecialTextSpanBuilder( + showAtBackground: true, + ), + controller: _textEditingController, + selectionControls: + _myExtendedMaterialTextSelectionControls, + extendedContextMenuBuilder: + MyTextSelectionControls.defaultContextMenuBuilder, + focusNode: _focusNode, + decoration: InputDecoration( + border: InputBorder.none, + suffixIcon: GestureDetector( + onTap: () { + sendMessage(_textEditingController.text); + _textEditingController.clear(); + }, + child: const Icon(Icons.send), + ), + contentPadding: const EdgeInsets.all(12.0), + ), + onTap: () { + _customKeyboardController.showSystemKeyboard(); + }, + //textDirection: TextDirection.rtl, + ), + Container(height: 1, color: Colors.grey.withOpacity(0.3)), + KeyboardTypeBuilder( + builder: ( + BuildContext context, + CustomKeyboardController controller, + ) => + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + createToolButton( + Icons.sentiment_very_satisfied, + KeyboardPanelType.emoji, + controller, + ), + createToolButton( + Icons.image, + KeyboardPanelType.image, + controller, + ), + createToolButton( + Icons.call_end, + KeyboardPanelType.at, + controller, + ), + createToolButton( + Icons.attach_money, + KeyboardPanelType.dollar, + controller, + ), + ], + ), + ), + ], + ), + ), + ], + ), + builder: (BuildContext context, double? systemKeyboardHeight) { + return _buildCustomKeyboard(context, systemKeyboardHeight); + }, + ), + ), + ), + ); + } + + Widget createToolButton( + IconData icon, + KeyboardPanelType keyboardPanelType, + CustomKeyboardController controller, + ) { + return ToggleButton( + builder: (bool active) => Icon( + icon, + color: active ? Colors.orange : null, + ), + activeChanged: (bool active) { + _keyboardPanelType = keyboardPanelType; + if (active) { + controller.showCustomKeyboard(); + if (!_focusNode.hasFocus) { + SchedulerBinding.instance + .addPostFrameCallback((Duration timeStamp) { + _focusNode.requestFocus(); + }); + } + } else { + controller.showSystemKeyboard(); + } + }, + active: controller.isCustom && _keyboardPanelType == keyboardPanelType, + ); + } + + void insertText(String text) { + _textEditingController.insertText(text); + + SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { + _key.currentState?.bringIntoView(_textEditingController.selection.base); + }); + } + + void manualDelete() { + final TextSpan oldTextSpan = + _mySpecialTextSpanBuilder.build(_textEditingController.text); + + final TextEditingValue value = + ExtendedTextLibraryUtils.handleSpecialTextSpanDelete( + _textEditingController.deleteText(), + _textEditingController.value, + oldTextSpan, + null, + ); + _textEditingController.value = value; + } + + void sendMessage(String text) { + if (text.isEmpty) { + return; + } + setState(() { + sessions.insert(0, text); + }); + SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { + _controller.animateTo( + _controller.position.minScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + ); + }); + } + + Widget _buildCustomKeyboard( + BuildContext context, + double? systemKeyboardHeight, + ) { + systemKeyboardHeight ??= 346; + + switch (_keyboardPanelType) { + case KeyboardPanelType.emoji: + return SizedBox( + height: systemKeyboardHeight, + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 8, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0), + itemBuilder: (BuildContext context, int index) { + return GestureDetector( + child: Image.asset( + emoji.EmojiUitl.instance.emojiMap['[${index + 1}]']!), + behavior: HitTestBehavior.translucent, + onTap: () { + insertText('[${index + 1}]'); + }, + ); + }, + itemCount: emoji.EmojiUitl.instance.emojiMap.length, + padding: const EdgeInsets.all(5.0), + ), + ); + case KeyboardPanelType.image: + return SizedBox( + height: systemKeyboardHeight, + child: LoadingMoreList( + ListConfig( + sourceList: imageList, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + ), + itemBuilder: (BuildContext context, TuChongItem item, int index) { + final String url = item.imageUrl; + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + // + + sendMessage( + ""); + }, + child: ExtendedImage.network( + url, + ), + ); + }, + padding: const EdgeInsets.all(5.0), + ), + ), + ); + case KeyboardPanelType.at: + return SizedBox( + height: systemKeyboardHeight, + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0), + itemBuilder: (BuildContext context, int index) { + final String text = atList[index]; + return GestureDetector( + child: Align( + child: Text(text), + ), + behavior: HitTestBehavior.translucent, + onTap: () { + insertText(text); + }, + ); + }, + itemCount: atList.length, + padding: const EdgeInsets.all(5.0), + ), + ); + case KeyboardPanelType.dollar: + return SizedBox( + height: systemKeyboardHeight, + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0), + itemBuilder: (BuildContext context, int index) { + final String text = dollarList[index]; + return GestureDetector( + child: Align( + child: Text(text.replaceAll('\$', '')), + ), + behavior: HitTestBehavior.translucent, + onTap: () { + insertText(text); + }, + ); + }, + itemCount: dollarList.length, + padding: const EdgeInsets.all(5.0), + ), + ); + default: + return Container(); + } + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/pages/main_page.dart b/local_packages/extended_text_field-16.0.2/example/lib/pages/main_page.dart new file mode 100644 index 0000000..eb077b4 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/pages/main_page.dart @@ -0,0 +1,172 @@ +import 'package:collection/collection.dart'; +import 'package:example/example_routes.dart'; +import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../example_route.dart'; +import '../example_routes.dart' as example_routes; + +@FFRoute( + name: 'fluttercandies://mainpage', + routeName: 'MainPage', +) +class MainPage extends StatelessWidget { + MainPage() { + final List routeNames = []; + routeNames.addAll(example_routes.routeNames); + routeNames.remove(Routes.fluttercandiesMainpage); + routeNames.remove(Routes.fluttercandiesDemogrouppage); + routesGroup.addAll(groupBy( + routeNames + .map((String name) => getRouteSettings(name: name)) + .where((FFRouteSettings element) => element.exts != null) + .map((FFRouteSettings e) => DemoRouteResult(e)) + .toList() + ..sort((DemoRouteResult a, DemoRouteResult b) => + b.group!.compareTo(a.group!)), + (DemoRouteResult x) => x.group)); + } + final Map> routesGroup = + >{}; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: const Text('extended_text_field'), + actions: [ + ButtonTheme( + minWidth: 0.0, + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: TextButton( + child: const Text( + 'Github', + style: TextStyle( + decorationStyle: TextDecorationStyle.solid, + decoration: TextDecoration.underline, + color: Colors.white, + ), + ), + onPressed: () { + launchUrl(Uri.parse( + 'https://github.com/fluttercandies/extended_text_field')); + }, + ), + ), + if (!kIsWeb) + ButtonTheme( + padding: const EdgeInsets.only(right: 10.0), + minWidth: 0.0, + child: TextButton( + child: Image.network( + 'https://pub.idqqimg.com/wpa/images/group.png'), + onPressed: () { + launchUrl(Uri.parse('https://jq.qq.com/?_wv=1027&k=5bcc0gy')); + }, + ), + ) + ], + ), + body: ListView.builder( + itemBuilder: (BuildContext c, int index) { + // final RouteResult page = routes[index]; + final String type = routesGroup.keys.toList()[index]!; + return Container( + margin: const EdgeInsets.all(20.0), + child: GestureDetector( + behavior: HitTestBehavior.translucent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + (index + 1).toString() + '.' + type, + //style: TextStyle(inherit: false), + ), + Text( + '$type demos of extended_text_field', + //page.description, + style: const TextStyle(color: Colors.grey), + ) + ], + ), + onTap: () { + Navigator.pushNamed( + context, + Routes.fluttercandiesDemogrouppage, + arguments: { + 'keyValue': routesGroup.entries.toList()[index], + }, + ); + }, + )); + }, + itemCount: routesGroup.length, + ), + ); + } +} + +@FFRoute( + name: 'fluttercandies://demogrouppage', + routeName: 'DemoGroupPage', +) +class DemoGroupPage extends StatelessWidget { + DemoGroupPage({required MapEntry> keyValue}) + : routes = keyValue.value + ..sort((DemoRouteResult a, DemoRouteResult b) => + a.order!.compareTo(b.order!)), + group = keyValue.key!; + final List routes; + final String group; + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('$group demos'), + ), + body: ListView.builder( + itemBuilder: (BuildContext context, int index) { + final DemoRouteResult page = routes[index]; + return Container( + margin: const EdgeInsets.all(20.0), + child: GestureDetector( + behavior: HitTestBehavior.translucent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + (index + 1).toString() + '.' + page.routeResult.routeName!, + //style: TextStyle(inherit: false), + ), + Text( + page.routeResult.description!, + style: const TextStyle(color: Colors.grey), + ) + ], + ), + onTap: () { + Navigator.pushNamed(context, page.routeResult.name!); + }, + ), + ); + }, + itemCount: routes.length, + ), + ); + } +} + +class DemoRouteResult { + DemoRouteResult( + this.routeResult, + ) : order = routeResult.exts!['order'] as int?, + group = routeResult.exts!['group'] as String?; + + final int? order; + final String? group; + final FFRouteSettings routeResult; +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/pages/simple/custom_toolbar.dart b/local_packages/extended_text_field-16.0.2/example/lib/pages/simple/custom_toolbar.dart new file mode 100644 index 0000000..a9e989b --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/pages/simple/custom_toolbar.dart @@ -0,0 +1,141 @@ +// ignore_for_file: always_put_control_body_on_new_line + +import 'package:example/special_text/my_extended_text_selection_controls.dart'; +import 'package:example/special_text/my_special_text_span_builder.dart'; +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; +import 'package:flutter/material.dart'; + +/// +/// create by zmtzawqlp on 2019/7/31 +/// + +@FFRoute( + name: 'fluttercandies://CustomToolBar', + routeName: 'custom toolbar', + description: 'custom selection toolbar and handles for text field', + exts: { + 'group': 'Simple', + 'order': 0, + }, +) +class CustomToolBar extends StatefulWidget { + @override + _CustomToolBarState createState() => _CustomToolBarState(); +} + +class _CustomToolBarState extends State { + final MyTextSelectionControls _myExtendedMaterialTextSelectionControls = + MyTextSelectionControls(); + final MySpecialTextSpanBuilder _mySpecialTextSpanBuilder = + MySpecialTextSpanBuilder(); + TextEditingController controller = TextEditingController() + ..text = + '[33]Extended text field help you to build rich text quickly. any special text you will have with extended text. this is demo to show how to create custom toolbar and handles.' + '\n\nIt\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[36]' + '\n\nif you meet any problem, please let me konw @zmtzawqlp .[44]'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('custom selection toolbar handles'), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), + child: Center( + child: ExtendedTextField( + selectionControls: _myExtendedMaterialTextSelectionControls, + specialTextSpanBuilder: _mySpecialTextSpanBuilder, + extendedContextMenuBuilder: + MyTextSelectionControls.defaultContextMenuBuilder, + controller: controller, + maxLines: null, + // StrutStyle get strutStyle { + // if (_strutStyle == null) { + // return StrutStyle.fromTextStyle(style, forceStrutHeight: true); + // } + // return _strutStyle!.inheritFromTextStyle(style); + // } + // default strutStyle is not good for WidgetSpan + strutStyle: const StrutStyle(), + // shouldShowSelectionHandles: _shouldShowSelectionHandles, + // textSelectionGestureDetectorBuilder: ({ + // required ExtendedTextSelectionGestureDetectorBuilderDelegate + // delegate, + // required Function showToolbar, + // required Function hideToolbar, + // required Function? onTap, + // required BuildContext context, + // required Function? requestKeyboard, + // }) { + // return MyCommonTextSelectionGestureDetectorBuilder( + // delegate: delegate, + // showToolbar: showToolbar, + // hideToolbar: hideToolbar, + // onTap: onTap, + // context: context, + // requestKeyboard: requestKeyboard, + // ); + // }, + ), + ), + ), + ); + } + +// bool _shouldShowSelectionHandles( +// SelectionChangedCause? cause, +// CommonTextSelectionGestureDetectorBuilder selectionGestureDetectorBuilder, +// TextEditingValue editingValue, +// ) { +// // When the text field is activated by something that doesn't trigger the +// // selection overlay, we shouldn't show the handles either. + +// // +// // if (!selectionGestureDetectorBuilder.shouldShowSelectionToolbar) +// // return false; + +// if (cause == SelectionChangedCause.keyboard) return false; + +// // if (widget.readOnly && _effectiveController.selection.isCollapsed) +// // return false; + +// // if (!_isEnabled) return false; + +// if (cause == SelectionChangedCause.longPress) return true; + +// if (editingValue.text.isNotEmpty) return true; + +// return false; +// } +} + +// class MyCommonTextSelectionGestureDetectorBuilder +// extends CommonTextSelectionGestureDetectorBuilder { +// MyCommonTextSelectionGestureDetectorBuilder( +// {required ExtendedTextSelectionGestureDetectorBuilderDelegate delegate, +// required Function showToolbar, +// required Function hideToolbar, +// required Function? onTap, +// required BuildContext context, +// required Function? requestKeyboard}) +// : super( +// delegate: delegate, +// showToolbar: showToolbar, +// hideToolbar: hideToolbar, +// onTap: onTap, +// context: context, +// requestKeyboard: requestKeyboard, +// ); +// @override +// void onTapDown(TapDragDownDetails details) { +// super.onTapDown(details); + +// /// always show toolbar +// shouldShowSelectionToolbar = true; +// } + +// @override +// bool get showToolbarInWeb => true; +// } diff --git a/local_packages/extended_text_field-16.0.2/example/lib/pages/simple/no_keyboard.dart b/local_packages/extended_text_field-16.0.2/example/lib/pages/simple/no_keyboard.dart new file mode 100644 index 0000000..49d755b --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/pages/simple/no_keyboard.dart @@ -0,0 +1,430 @@ +import 'package:extended_keyboard/extended_keyboard.dart'; +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:oktoast/oktoast.dart'; + +class CustomKeyboarBinding extends TextInputBinding with KeyboardBindingMixin { + @override + // ignore: unnecessary_overrides + bool ignoreTextInputShow() { + // you can override it base on your case + // if NoKeyboardFocusNode is not enough + return super.ignoreTextInputShow(); + } +} + +@FFRoute( + name: 'fluttercandies://NoKeyboard', + routeName: 'no system Keyboard', + description: 'show how to ignore system keyboard and show custom keyboard', + exts: { + 'group': 'Simple', + 'order': 2, + }, +) +class NoSystemKeyboardDemo extends StatelessWidget { + const NoSystemKeyboardDemo({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('no system Keyboard'), + ), + body: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + FocusManager.instance.primaryFocus?.unfocus(); + }, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Column(children: [ + Text('ExtendedTextField'), + ExtendedTextFieldCase(), + Text('CustomTextField'), + TextFieldCase(), + ]), + ), + ), + ); + } +} + +class ExtendedTextFieldCase extends StatefulWidget { + const ExtendedTextFieldCase({Key? key}) : super(key: key); + + @override + State createState() => _ExtendedTextFieldCaseState(); +} + +class _ExtendedTextFieldCaseState extends State + with CustomKeyboardShowStateMixin { + @override + Widget build(BuildContext context) { + return ExtendedTextField( + // you must use TextInputFocusNode + focusNode: _focusNode..debugLabel = 'ExtendedTextField', + // if your custom keyboard can be close without unfocus + // you can show custom keyboard when TextField onTap + controller: _controller, + maxLines: null, + inputFormatters: _inputFormatters, + ); + } +} + +class TextFieldCase extends StatefulWidget { + const TextFieldCase({Key? key}) : super(key: key); + @override + State createState() => TextFieldCaseState(); +} + +class TextFieldCaseState extends State + with CustomKeyboardShowStateMixin { + @override + Widget build(BuildContext context) { + return TextField( + // you must use TextInputFocusNode + focusNode: _focusNode..debugLabel = 'CustomTextField', + // if your custom keyboard can be close without unfocus + // you can show custom keyboard when TextField onTap + onTap: _onTextFiledTap, + controller: _controller, + inputFormatters: _inputFormatters, + maxLines: null, + ); + } +} + +@optionalTypeArgs +mixin CustomKeyboardShowStateMixin on State { + final TextInputFocusNode _focusNode = TextInputFocusNode(); + final TextEditingController _controller = TextEditingController(); + PersistentBottomSheetController? _bottomSheetController; + + final List _inputFormatters = [ + // digit or decimal + FilteringTextInputFormatter.allow(RegExp(r'[1-9]{1}[0-9.]*')), + // only one decimal + TextInputFormatter.withFunction( + (TextEditingValue oldValue, TextEditingValue newValue) => + newValue.text.indexOf('.') != newValue.text.lastIndexOf('.') + ? oldValue + : newValue), + ]; + + @override + void initState() { + super.initState(); + _focusNode.addListener(_handleFocusChanged); + } + + void _onTextFiledTap() { + if (_bottomSheetController == null) { + _handleFocusChanged(); + } + } + + void _handleFocusChanged() { + if (_focusNode.hasFocus) { + // just demo, you can define your custom keyboard as you want + _bottomSheetController = showBottomSheet( + context: FocusManager.instance.primaryFocus!.context!, + // set false, if don't want to drag to close custom keyboard + enableDrag: true, + builder: (BuildContext b) { + final MediaQueryData mediaQueryData = MediaQuery.of(b); + + return Material( + //shadowColor: Colors.grey, + color: Colors.grey.withOpacity(0.3), + //elevation: 8, + child: Padding( + padding: EdgeInsets.only( + left: 10, + right: 10, + top: 20, + bottom: mediaQueryData.viewPadding.bottom / + mediaQueryData.devicePixelRatio, + ), + child: IntrinsicHeight( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + flex: 15, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + flex: 5, + child: NumberButton( + number: 1, + insertText: insertText, + ), + ), + Expanded( + flex: 5, + child: NumberButton( + number: 2, + insertText: insertText, + ), + ), + Expanded( + flex: 5, + child: NumberButton( + number: 3, + insertText: insertText, + ), + ), + ], + ), + Row( + children: [ + Expanded( + flex: 5, + child: NumberButton( + number: 4, + insertText: insertText, + ), + ), + Expanded( + flex: 5, + child: NumberButton( + number: 5, + insertText: insertText, + ), + ), + Expanded( + flex: 5, + child: NumberButton( + number: 6, + insertText: insertText, + ), + ), + ], + ), + Row( + children: [ + Expanded( + flex: 5, + child: NumberButton( + number: 7, + insertText: insertText, + ), + ), + Expanded( + flex: 5, + child: NumberButton( + number: 8, + insertText: insertText, + ), + ), + Expanded( + flex: 5, + child: NumberButton( + number: 9, + insertText: insertText, + ), + ), + ], + ), + Row( + children: [ + Expanded( + flex: 5, + child: CustomButton( + child: const Text('.'), + onTap: () { + insertText('.'); + }, + ), + ), + Expanded( + flex: 5, + child: NumberButton( + number: 0, + insertText: insertText, + ), + ), + Expanded( + flex: 5, + child: CustomButton( + child: const Icon(Icons.arrow_downward), + onTap: () { + _focusNode.unfocus(); + }, + ), + ), + ], + ), + ], + ), + ), + Expanded( + flex: 7, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: CustomButton( + child: const Icon(Icons.backspace), + onTap: () { + manualDelete(); + }, + )), + Expanded( + child: CustomButton( + child: const Icon(Icons.keyboard_return), + onTap: () { + showToast('onSubmitted: ${_controller.text}'); + }, + )) + ], + ), + ), + ], + ), + ), + ), + ); + }); + // maybe drag close + _bottomSheetController?.closed.whenComplete(() { + _bottomSheetController = null; + }); + } else { + _bottomSheetController?.close(); + _bottomSheetController = null; + } + } + + @override + void dispose() { + _focusNode.removeListener(_handleFocusChanged); + super.dispose(); + } + + void insertText(String text) { + final TextEditingValue oldValue = _controller.value; + TextEditingValue newValue = oldValue; + final int start = oldValue.selection.baseOffset; + int end = oldValue.selection.extentOffset; + if (oldValue.selection.isValid) { + String newText = ''; + if (oldValue.selection.isCollapsed) { + if (end > 0) { + newText += oldValue.text.substring(0, end); + } + newText += text; + if (oldValue.text.length > end) { + newText += oldValue.text.substring(end, oldValue.text.length); + } + } else { + newText = oldValue.text.replaceRange(start, end, text); + end = start; + } + + newValue = oldValue.copyWith( + text: newText, + selection: oldValue.selection.copyWith( + baseOffset: end + text.length, extentOffset: end + text.length)); + } else { + newValue = TextEditingValue( + text: text, + selection: + TextSelection.fromPosition(TextPosition(offset: text.length))); + } + for (final TextInputFormatter inputFormatter in _inputFormatters) { + newValue = inputFormatter.formatEditUpdate(oldValue, newValue); + } + + _controller.value = newValue; + } + + void manualDelete() { + //delete by code + final TextEditingValue _value = _controller.value; + final TextSelection selection = _value.selection; + if (!selection.isValid) { + return; + } + + TextEditingValue value; + final String actualText = _value.text; + if (selection.isCollapsed && selection.start == 0) { + return; + } + final int start = + selection.isCollapsed ? selection.start - 1 : selection.start; + final int end = selection.end; + + value = TextEditingValue( + text: actualText.replaceRange(start, end, ''), + selection: TextSelection.collapsed(offset: start), + ); + + _controller.value = value; + } +} + +class NumberButton extends StatelessWidget { + const NumberButton({ + Key? key, + required this.number, + required this.insertText, + }) : super(key: key); + final int number; + final Function(String text) insertText; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + insertText('$number'); + }, + child: Container( + margin: const EdgeInsets.all(5), + alignment: Alignment.center, + height: 50, + child: Text( + '$number', + ), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8), + ), + ), + ); + } +} + +class CustomButton extends StatelessWidget { + const CustomButton({ + Key? key, + required this.child, + required this.onTap, + }) : super(key: key); + final Widget child; + final GestureTapCallback onTap; + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + margin: const EdgeInsets.all(5), + alignment: Alignment.center, + height: 50, + child: child, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8), + ), + ), + ); + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/pages/simple/selectable_text.dart b/local_packages/extended_text_field-16.0.2/example/lib/pages/simple/selectable_text.dart new file mode 100644 index 0000000..b24aef0 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/pages/simple/selectable_text.dart @@ -0,0 +1,33 @@ +import 'package:example/special_text/my_special_text_span_builder.dart'; +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; +import 'package:flutter/material.dart'; + +@FFRoute( + name: 'fluttercandies://SelectableTextDemo', + routeName: 'SelectableText', + description: 'support SelectableText', + exts: { + 'group': 'Simple', + 'order': 3, + }, +) +class SelectableTextDemo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('SelectableText'), + ), + body: Container( + padding: const EdgeInsets.all(20.0), + child: ExtendedSelectableText( + '[17]Extended text help you to build rich text quickly. any special text you will have with extended text. ' + '\n\nIt\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[17]' + '\n\nif you meet any problem, please let me know @zmtzawqlp .[36]', + specialTextSpanBuilder: MySpecialTextSpanBuilder(), + ), + ), + ); + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/pages/simple/widget_span.dart b/local_packages/extended_text_field-16.0.2/example/lib/pages/simple/widget_span.dart new file mode 100644 index 0000000..b60b5c4 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/pages/simple/widget_span.dart @@ -0,0 +1,194 @@ +import 'package:example/special_text/email_span_builder.dart'; +import 'package:example/special_text/my_special_text_span_builder.dart'; +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; +import 'package:flutter/material.dart'; + +/// +/// create by zmtzawqlp on 2019/8/4 +/// +@FFRoute( + name: 'fluttercandies://WidgetSpanDemo', + routeName: 'widget span', + description: 'mailbox demo with widgetSpan', + exts: { + 'group': 'Simple', + 'order': 1, + }, +) +class WidgetSpanDemo extends StatefulWidget { + @override + _WidgetSpanDemoState createState() => _WidgetSpanDemoState(); +} + +class _WidgetSpanDemoState extends State { + TextEditingController controller = TextEditingController(); + TextEditingController controller1 = TextEditingController(); + TextEditingController controller2 = TextEditingController() + ..text = + '[33]Extended text field help you to build rich text quickly. any special text you will have with extended text field. this is demo to show how to create special text with widget span.' + '\n\nIt\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[36]' + '\n\nif you meet any problem, please let me konw @zmtzawqlp .[44]'; + EmailSpanBuilder? _emailSpanBuilder; + @override + void initState() { + _emailSpanBuilder = EmailSpanBuilder(controller, context); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('E-mail'), + actions: [ + IconButton( + icon: const Icon(Icons.send), + onPressed: () {}, + ) + ], + ), + body: Column( + children: [ + Row( + children: [ + Container( + child: const Text('To : '), + width: 60.0, + padding: const EdgeInsets.only( + left: 10.0, + ), + ), + Expanded( + child: ExtendedTextField( + controller: controller, + specialTextSpanBuilder: _emailSpanBuilder, + maxLines: null, + // StrutStyle get strutStyle { + // if (_strutStyle == null) { + // return StrutStyle.fromTextStyle(style, forceStrutHeight: true); + // } + // return _strutStyle!.inheritFromTextStyle(style); + // } + // default strutStyle is not good for WidgetSpan + strutStyle: const StrutStyle(), + decoration: InputDecoration( + suffixIcon: IconButton( + icon: const Icon(Icons.add), + onPressed: () { + final TextSelection selection = + controller.selection.copyWith(); + showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext c) { + return Column( + children: [ + Flexible( + child: Container(), + ), + Expanded( + child: Material( + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + children: [ + TextButton( + onPressed: () { + insertEmail( + 'zmtzawqlp@live.com ', + selection); + Navigator.pop(context); + }, + child: const Text( + 'zmtzawqlp@live.com')), + TextButton( + onPressed: () { + insertEmail( + '410496936@qq.com ', + selection); + Navigator.pop(context); + }, + child: const Text( + '410496936@qq.com')), + ], + ), + )), + ), + Flexible( + child: Container(), + ) + ], + ); + }); + }, + ), + border: InputBorder.none, + hintText: 'input receiver here'), + ), + ), + ], + ), + const Divider(), + Row( + children: [ + Container( + child: const Text('Topic : '), + width: 60.0, + padding: const EdgeInsets.only(left: 10.0), + ), + Expanded( + child: ExtendedTextField( + controller: controller1, + maxLines: null, + decoration: const InputDecoration( + border: InputBorder.none, hintText: 'input topic here'), + ), + ) + ], + ), + const Divider(), + Expanded( + child: ExtendedTextField( + controller: controller2, + maxLines: null, + specialTextSpanBuilder: MySpecialTextSpanBuilder(), + decoration: const InputDecoration( + border: InputBorder.none, contentPadding: EdgeInsets.all(10)), + ), + ) + ], + ), + ); + } + + void insertEmail(String text, TextSelection selection) { + final TextEditingValue value = controller.value; + final int start = selection.baseOffset; + int end = selection.extentOffset; + if (selection.isValid) { + String newText = ''; + if (value.selection.isCollapsed) { + if (end > 0) { + newText += value.text.substring(0, end); + } + newText += text; + if (value.text.length > end) { + newText += value.text.substring(end, value.text.length); + } + } else { + newText = value.text.replaceRange(start, end, text); + end = start; + } + controller.value = value.copyWith( + text: newText, + selection: selection.copyWith( + baseOffset: end + text.length, extentOffset: end + text.length)); + } else { + controller.value = TextEditingValue( + text: text, + selection: + TextSelection.fromPosition(TextPosition(offset: text.length))); + } + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/special_text/at_text.dart b/local_packages/extended_text_field-16.0.2/example/lib/special_text/at_text.dart new file mode 100644 index 0000000..d80ab3e --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/special_text/at_text.dart @@ -0,0 +1,62 @@ +import 'package:extended_text_library/extended_text_library.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +class AtText extends SpecialText { + AtText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap, + {this.showAtBackground = false, this.start}) + : super(flag, ' ', textStyle, onTap: onTap); + static const String flag = '@'; + final int? start; + + /// whether show background for @somebody + final bool showAtBackground; + + @override + InlineSpan finishText() { + final TextStyle? textStyle = + this.textStyle?.copyWith(color: Colors.blue, fontSize: 16.0); + + final String atText = toString(); + + return showAtBackground + ? BackgroundTextSpan( + background: Paint()..color = Colors.blue.withOpacity(0.15), + text: atText, + actualText: atText, + start: start!, + + ///caret can move into special text + deleteAll: true, + style: textStyle, + recognizer: (TapGestureRecognizer() + ..onTap = () { + if (onTap != null) { + onTap!(atText); + } + })) + : SpecialTextSpan( + text: atText, + actualText: atText, + start: start!, + style: textStyle, + recognizer: (TapGestureRecognizer() + ..onTap = () { + if (onTap != null) { + onTap!(atText); + } + })); + } +} + +List atList = [ + '@Nevermore ', + '@Dota2 ', + '@Biglao ', + '@艾莉亚·史塔克 ', + '@丹妮莉丝 ', + '@HandPulledNoodles ', + '@Zmtzawqlp ', + '@FaDeKongJian ', + '@CaiJingLongDaLao ', +]; diff --git a/local_packages/extended_text_field-16.0.2/example/lib/special_text/dollar_text.dart b/local_packages/extended_text_field-16.0.2/example/lib/special_text/dollar_text.dart new file mode 100644 index 0000000..989feae --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/special_text/dollar_text.dart @@ -0,0 +1,43 @@ +import 'package:extended_text_library/extended_text_library.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +class DollarText extends SpecialText { + DollarText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap, + {this.start}) + : super(flag, flag, textStyle, onTap: onTap); + static const String flag = '\$'; + final int? start; + @override + InlineSpan finishText() { + final String text = getContent(); + + return SpecialTextSpan( + text: text, + actualText: toString(), + start: start!, + + ///caret can move into special text + deleteAll: true, + style: textStyle?.copyWith(color: Colors.orange), + recognizer: TapGestureRecognizer() + ..onTap = () { + if (onTap != null) { + onTap!(toString()); + } + }); + } +} + +List dollarList = [ + '\$Dota2\$', + '\$Dota2 Ti9\$', + '\$CN dota best dota\$', + '\$Flutter\$', + '\$CN dev best dev\$', + '\$UWP\$', + '\$Nevermore\$', + '\$FlutterCandies\$', + '\$ExtendedImage\$', + '\$ExtendedText\$', +]; diff --git a/local_packages/extended_text_field-16.0.2/example/lib/special_text/email_span_builder.dart b/local_packages/extended_text_field-16.0.2/example/lib/special_text/email_span_builder.dart new file mode 100644 index 0000000..06aab1c --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/special_text/email_span_builder.dart @@ -0,0 +1,32 @@ +import 'package:extended_text_library/extended_text_library.dart'; +import 'package:flutter/material.dart'; + +import 'email_text.dart'; + +/// +/// create by zmtzawqlp on 2019/8/4 +/// + +class EmailSpanBuilder extends SpecialTextSpanBuilder { + EmailSpanBuilder(this.controller, this.context); + final TextEditingController controller; + final BuildContext context; + @override + SpecialText? createSpecialText(String flag, + {TextStyle? textStyle, + SpecialTextGestureTapCallback? onTap, + int? index}) { + if (flag == '') { + return null; + } + + if (!flag.startsWith(' ') && !flag.startsWith('@')) { + return EmailText(textStyle!, onTap, + start: index, + context: context, + controller: controller, + startFlag: flag); + } + return null; + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/special_text/email_text.dart b/local_packages/extended_text_field-16.0.2/example/lib/special_text/email_text.dart new file mode 100644 index 0000000..47c47cb --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/special_text/email_text.dart @@ -0,0 +1,115 @@ +import 'dart:ui' as ui show PlaceholderAlignment; +import 'package:extended_text_library/extended_text_library.dart'; +import 'package:flutter/material.dart'; + +class EmailText extends SpecialText { + EmailText(TextStyle textStyle, SpecialTextGestureTapCallback? onTap, + {this.start, this.controller, this.context, required String startFlag}) + : super(startFlag, ' ', textStyle, onTap: onTap); + final TextEditingController? controller; + final int? start; + final BuildContext? context; + @override + bool isEnd(String value) { + final int index = value.indexOf('@'); + final int index1 = value.indexOf('.'); + + return index >= 0 && + index1 >= 0 && + index1 > index + 1 && + super.isEnd(value); + } + + @override + InlineSpan finishText() { + final String text = toString(); + + return ExtendedWidgetSpan( + actualText: text, + start: start!, + alignment: ui.PlaceholderAlignment.middle, + child: GestureDetector( + child: Padding( + padding: const EdgeInsets.only(right: 5.0, top: 2.0, bottom: 2.0), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(5.0)), + child: Container( + padding: const EdgeInsets.all(5.0), + color: Colors.orange, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + text.trim(), + //style: textStyle?.copyWith(color: Colors.orange), + ), + const SizedBox( + width: 5.0, + ), + InkWell( + child: const Icon( + Icons.close, + size: 15.0, + ), + onTap: () { + controller!.value = controller!.value.copyWith( + text: controller!.text + .replaceRange(start!, start! + text.length, ''), + selection: TextSelection.fromPosition( + TextPosition(offset: start!))); + }, + ) + ], + ), + )), + ), + onTap: () { + showDialog( + context: context!, + barrierDismissible: true, + builder: (BuildContext c) { + final TextEditingController textEditingController = + TextEditingController()..text = text.trim(); + return Column( + children: [ + Expanded( + child: Container(), + ), + Material( + child: Padding( + padding: const EdgeInsets.all(10.0), + child: TextField( + controller: textEditingController, + decoration: InputDecoration( + suffixIcon: TextButton( + child: const Text('OK'), + onPressed: () { + controller!.value = controller!.value.copyWith( + text: controller!.text.replaceRange( + start!, + start! + text.length, + textEditingController.text + ' '), + selection: TextSelection.fromPosition( + TextPosition( + offset: start! + + (textEditingController.text + ' ') + .length))); + + Navigator.pop(context!); + }, + )), + ), + )), + Expanded( + child: Container(), + ) + ], + ); + }); + }, + ), + deleteAll: true, + ); + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/special_text/emoji_text.dart b/local_packages/extended_text_field-16.0.2/example/lib/special_text/emoji_text.dart new file mode 100644 index 0000000..8181291 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/special_text/emoji_text.dart @@ -0,0 +1,52 @@ +import 'package:extended_text_library/extended_text_library.dart'; +import 'package:flutter/material.dart'; + +///emoji/image text +class EmojiText extends SpecialText { + EmojiText(TextStyle? textStyle, {this.start}) + : super(EmojiText.flag, ']', textStyle); + static const String flag = '['; + final int? start; + @override + InlineSpan finishText() { + final String key = toString(); + + if (EmojiUitl.instance.emojiMap.containsKey(key)) { + double size = 18; + + if (textStyle?.fontSize != null) { + size = textStyle!.fontSize! * 1.15; + } + + return ImageSpan( + AssetImage( + EmojiUitl.instance.emojiMap[key]!, + ), + actualText: key, + imageWidth: size, + imageHeight: size, + start: start!, + //fit: BoxFit.fill, + margin: const EdgeInsets.all(2)); + } + + return TextSpan(text: toString(), style: textStyle); + } +} + +class EmojiUitl { + EmojiUitl._() { + for (int i = 1; i < 49; i++) { + _emojiMap['[$i]'] = '$_emojiFilePath/$i.png'; + } + } + + final Map _emojiMap = {}; + + Map get emojiMap => _emojiMap; + + final String _emojiFilePath = 'assets'; + + static EmojiUitl? _instance; + static EmojiUitl get instance => _instance ??= EmojiUitl._(); +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/special_text/image_text.dart b/local_packages/extended_text_field-16.0.2/example/lib/special_text/image_text.dart new file mode 100644 index 0000000..07c0520 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/special_text/image_text.dart @@ -0,0 +1,88 @@ +import 'dart:math'; + +import 'package:extended_image/extended_image.dart'; +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:flutter/material.dart' hide Element; +import 'package:html/dom.dart' hide Text; +import 'package:html/parser.dart'; + +class ImageText extends SpecialText { + ImageText(TextStyle? textStyle, + {this.start, SpecialTextGestureTapCallback? onTap}) + : super( + ImageText.flag, + '/>', + textStyle, + onTap: onTap, + ); + + static const String flag = ' _imageUrl; + @override + InlineSpan finishText() { + ///content already has endflag '/' + final String text = toString(); + + ///'' +// var index1 = text.indexOf(''') + 1; +// var index2 = text.indexOf(''', index1); +// +// var url = text.substring(index1, index2); +// + ////'' + final Document html = parse(text); + + final Element img = html.getElementsByTagName('img').first; + final String url = img.attributes['src']!; + _imageUrl = url; + + //fontsize id define image height + //size = 30.0/26.0 * fontSize + double? width = 60.0; + double? height = 60.0; + const BoxFit fit = BoxFit.cover; + const double num300 = 60.0; + const double num400 = 80.0; + + height = num300; + width = num400; + const bool knowImageSize = true; + if (knowImageSize) { + height = double.tryParse(img.attributes['height']!); + width = double.tryParse(img.attributes['width']!); + final double n = height! / width!; + if (n >= 4 / 3) { + width = num300; + height = num400; + } else if (4 / 3 > n && n > 3 / 4) { + final double maxValue = max(width, height); + height = num400 * height / maxValue; + width = num400 * width / maxValue; + } else if (n <= 3 / 4) { + width = num400; + height = num300; + } + } + + ///fontSize 26 and text height =30.0 + //final double fontSize = 26.0; + + return ExtendedWidgetSpan( + start: start!, + actualText: text, + child: GestureDetector( + onTap: () { + onTap?.call(url); + }, + child: ExtendedImage.network( + url, + width: width, + height: height, + fit: fit, + ), + ), + ); + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/special_text/my_extended_text_selection_controls.dart b/local_packages/extended_text_field-16.0.2/example/lib/special_text/my_extended_text_selection_controls.dart new file mode 100644 index 0000000..48d4ad7 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/special_text/my_extended_text_selection_controls.dart @@ -0,0 +1,106 @@ +import 'dart:math' as math; +import 'package:extended_text_field/extended_text_field.dart'; + +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +/// +/// create by zmtzawqlp on 2019/8/3 +/// + +const double _kHandleSize = 22.0; + +/// Android Material styled text selection controls. +class MyTextSelectionControls extends TextSelectionControls + with TextSelectionHandleControls { + static Widget defaultContextMenuBuilder( + BuildContext context, ExtendedEditableTextState editableTextState) { + return AdaptiveTextSelectionToolbar.buttonItems( + buttonItems: [ + ...editableTextState.contextMenuButtonItems, + ContextMenuButtonItem( + onPressed: () { + launchUrl( + Uri.parse( + 'mailto:xxx@live.com?subject=extended_text_share&body=${editableTextState.textEditingValue.text}', + ), + ); + editableTextState.hideToolbar(true); + editableTextState.textEditingValue + .copyWith(selection: const TextSelection.collapsed(offset: 0)); + }, + type: ContextMenuButtonType.custom, + label: 'like', + ), + ], + anchors: editableTextState.contextMenuAnchors, + ); + // return AdaptiveTextSelectionToolbar.editableText( + // editableTextState: editableTextState, + // ); + } + + /// Returns the size of the Material handle. + @override + Size getHandleSize(double textLineHeight) => + const Size(_kHandleSize, _kHandleSize); + + /// Builder for material-style text selection handles. + @override + Widget buildHandle( + BuildContext context, TextSelectionHandleType type, double textLineHeight, + [VoidCallback? onTap, double? startGlyphHeight, double? endGlyphHeight]) { + final Widget handle = SizedBox( + width: _kHandleSize, + height: _kHandleSize, + child: Image.asset( + 'assets/40.png', + ), + ); + + // [handle] is a circle, with a rectangle in the top left quadrant of that + // circle (an onion pointing to 10:30). We rotate [handle] to point + // straight up or up-right depending on the handle type. + switch (type) { + case TextSelectionHandleType.left: // points up-right + return Transform.rotate( + angle: math.pi / 4.0, + child: handle, + ); + case TextSelectionHandleType.right: // points up-left + return Transform.rotate( + angle: -math.pi / 4.0, + child: handle, + ); + case TextSelectionHandleType.collapsed: // points up + return handle; + } + } + + /// Gets anchor for material-style text selection handles. + /// + /// See [TextSelectionControls.getHandleAnchor]. + @override + Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight, + [double? startGlyphHeight, double? endGlyphHeight]) { + switch (type) { + case TextSelectionHandleType.left: + return const Offset(_kHandleSize, 0); + case TextSelectionHandleType.right: + return Offset.zero; + default: + return const Offset(_kHandleSize / 2, -4); + } + } + + @override + bool canSelectAll(TextSelectionDelegate delegate) { + // Android allows SelectAll when selection is not collapsed, unless + // everything has already been selected. + final TextEditingValue value = delegate.textEditingValue; + return delegate.selectAllEnabled && + value.text.isNotEmpty && + !(value.selection.start == 0 && + value.selection.end == value.text.length); + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/lib/special_text/my_special_text_span_builder.dart b/local_packages/extended_text_field-16.0.2/example/lib/special_text/my_special_text_span_builder.dart new file mode 100644 index 0000000..ae1bbe7 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/lib/special_text/my_special_text_span_builder.dart @@ -0,0 +1,45 @@ +import 'package:example/special_text/image_text.dart'; +import 'package:extended_text_library/extended_text_library.dart'; +import 'package:flutter/material.dart'; + +import 'at_text.dart'; +import 'dollar_text.dart'; +import 'emoji_text.dart'; + +class MySpecialTextSpanBuilder extends SpecialTextSpanBuilder { + MySpecialTextSpanBuilder({this.showAtBackground = false}); + + /// whether show background for @somebody + final bool showAtBackground; + + @override + SpecialText? createSpecialText(String flag, + {TextStyle? textStyle, + SpecialTextGestureTapCallback? onTap, + int? index}) { + if (flag == '') { + return null; + } + + ///index is end index of start flag, so text start index should be index-(flag.length-1) + if (isStart(flag, EmojiText.flag)) { + return EmojiText(textStyle, start: index! - (EmojiText.flag.length - 1)); + } else if (isStart(flag, ImageText.flag)) { + return ImageText(textStyle, + start: index! - (ImageText.flag.length - 1), onTap: onTap); + } else if (isStart(flag, AtText.flag)) { + return AtText( + textStyle, + onTap, + start: index! - (AtText.flag.length - 1), + showAtBackground: showAtBackground, + ); + } else if (isStart(flag, EmojiText.flag)) { + return EmojiText(textStyle, start: index! - (EmojiText.flag.length - 1)); + } else if (isStart(flag, DollarText.flag)) { + return DollarText(textStyle, onTap, + start: index! - (DollarText.flag.length - 1)); + } + return null; + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Flutter/Flutter-Debug.xcconfig b/local_packages/extended_text_field-16.0.2/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Flutter/Flutter-Release.xcconfig b/local_packages/extended_text_field-16.0.2/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Flutter/GeneratedPluginRegistrant.swift b/local_packages/extended_text_field-16.0.2/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..8236f57 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Podfile b/local_packages/extended_text_field-16.0.2/example/macos/Podfile new file mode 100644 index 0000000..049abe2 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Podfile @@ -0,0 +1,40 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Podfile.lock b/local_packages/extended_text_field-16.0.2/example/macos/Podfile.lock new file mode 100644 index 0000000..eb85594 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - FlutterMacOS (1.0.0) + - url_launcher_macos (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + +SPEC CHECKSUMS: + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + url_launcher_macos: c04e4fa86382d4f94f6b38f14625708be3ae52e2 + +PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 + +COCOAPODS: 1.11.2 diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcodeproj/project.pbxproj b/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ce99738 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,633 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 7AF4D8477C1FAF78892E7B47 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F20835456926299CD3CDCE5 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8A07DB08724BE1ACDF26F964 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 9F20835456926299CD3CDCE5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D7B7FF68CC01EE21C6512FAA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + FABA45EBEEAD51CFF206571D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7AF4D8477C1FAF78892E7B47 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + E5F3109C001394DBA2121E0A /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 9F20835456926299CD3CDCE5 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E5F3109C001394DBA2121E0A /* Pods */ = { + isa = PBXGroup; + children = ( + FABA45EBEEAD51CFF206571D /* Pods-Runner.debug.xcconfig */, + 8A07DB08724BE1ACDF26F964 /* Pods-Runner.release.xcconfig */, + D7B7FF68CC01EE21C6512FAA /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 3EF585B484D5C35B2252DA86 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + BF4155FDD3B79E6AF9E31812 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 3EF585B484D5C35B2252DA86 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + BF4155FDD3B79E6AF9E31812 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..fb7259e --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/AppDelegate.swift b/local_packages/extended_text_field-16.0.2/example/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..3c4935a Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..ed4cc16 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..483be61 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bcbf36d Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..9c0a652 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..e71a726 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..8a31fe2 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Base.lproj/MainMenu.xib b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Configs/AppInfo.xcconfig b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..8b42559 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Configs/Debug.xcconfig b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Configs/Release.xcconfig b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Configs/Warnings.xcconfig b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/DebugProfile.entitlements b/local_packages/extended_text_field-16.0.2/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dcf4060 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.network.client + + + diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Info.plist b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/MainFlutterWindow.swift b/local_packages/extended_text_field-16.0.2/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..2722837 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/macos/Runner/Release.entitlements b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Release.entitlements new file mode 100644 index 0000000..1f6330d --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/macos/Runner/Release.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/local_packages/extended_text_field-16.0.2/example/pubspec.yaml b/local_packages/extended_text_field-16.0.2/example/pubspec.yaml new file mode 100644 index 0000000..62233ad --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/pubspec.yaml @@ -0,0 +1,99 @@ +name: example +description: A new Flutter application. +publish_to: none +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: '>=3.4.0 <4.0.0' + flutter: ">=3.22.0" +dependencies: + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + + + cupertino_icons: ^1.0.4 + + ff_annotation_route_library: ^3.0.0 + extended_text: ^14.0.1 + # extended_text: + # version: ^11.0.0-dev.1 + # hosted: "https://pub.dev" + flutter: + sdk: flutter + html: ^0.15.0 + loading_more_list: ^7.1.0 + oktoast: ^3.1.5 + url_launcher: ^6.0.17 + extended_keyboard: ^0.0.3 + extended_image: any + +dev_dependencies: + flutter_test: + sdk: flutter +dependency_overrides: + extended_text_field: + path: ../ + + #extended_text: + # git: + # url: https://github.com/fluttercandies/extended_text.git + # ref: refactor + #path: ../../extended_text + #extended_text_library: + # git: + # url: https://github.com/fluttercandies/extended_text_library.git + # ref: refactor + #path: ../../extended_text_library +# For information on the generic Dart part of this file, see the +# following page: https://www.dartlang.org/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + assets: + - assets/ + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/local_packages/extended_text_field-16.0.2/example/web/favicon.png b/local_packages/extended_text_field-16.0.2/example/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/web/favicon.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/web/icons/Icon-192.png b/local_packages/extended_text_field-16.0.2/example/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/web/icons/Icon-192.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/web/icons/Icon-512.png b/local_packages/extended_text_field-16.0.2/example/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/web/icons/Icon-512.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/web/icons/Icon-maskable-192.png b/local_packages/extended_text_field-16.0.2/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/web/icons/Icon-maskable-192.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/web/icons/Icon-maskable-512.png b/local_packages/extended_text_field-16.0.2/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/web/icons/Icon-maskable-512.png differ diff --git a/local_packages/extended_text_field-16.0.2/example/web/index.html b/local_packages/extended_text_field-16.0.2/example/web/index.html new file mode 100644 index 0000000..b6b9dd2 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/web/index.html @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/web/manifest.json b/local_packages/extended_text_field-16.0.2/example/web/manifest.json new file mode 100644 index 0000000..096edf8 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/local_packages/extended_text_field-16.0.2/example/windows/CMakeLists.txt b/local_packages/extended_text_field-16.0.2/example/windows/CMakeLists.txt new file mode 100644 index 0000000..abf9040 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.15) +project(example LANGUAGES CXX) + +set(BINARY_NAME "example") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Configure build options. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() + +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/local_packages/extended_text_field-16.0.2/example/windows/flutter/CMakeLists.txt b/local_packages/extended_text_field-16.0.2/example/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..744f08a --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,102 @@ +cmake_minimum_required(VERSION 3.15) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/local_packages/extended_text_field-16.0.2/example/windows/flutter/generated_plugin_registrant.cc b/local_packages/extended_text_field-16.0.2/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..4f78848 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/local_packages/extended_text_field-16.0.2/example/windows/flutter/generated_plugin_registrant.h b/local_packages/extended_text_field-16.0.2/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/local_packages/extended_text_field-16.0.2/example/windows/flutter/generated_plugins.cmake b/local_packages/extended_text_field-16.0.2/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..88b22e5 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/CMakeLists.txt b/local_packages/extended_text_field-16.0.2/example/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..977e38b --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/runner/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.15) +project(runner LANGUAGES CXX) + +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "run_loop.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) +apply_standard_settings(${BINARY_NAME}) +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/Runner.rc b/local_packages/extended_text_field-16.0.2/example/windows/runner/Runner.rc new file mode 100644 index 0000000..944329a --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#else +#define VERSION_AS_NUMBER 1,0,0 +#endif + +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "A new Flutter project." "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2020 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/flutter_window.cpp b/local_packages/extended_text_field-16.0.2/example/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..c422723 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/runner/flutter_window.cpp @@ -0,0 +1,64 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(RunLoop* run_loop, + const flutter::DartProject& project) + : run_loop_(run_loop), project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opporutunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/flutter_window.h b/local_packages/extended_text_field-16.0.2/example/windows/runner/flutter_window.h new file mode 100644 index 0000000..b663ddd --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/runner/flutter_window.h @@ -0,0 +1,39 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "run_loop.h" +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow driven by the |run_loop|, hosting a + // Flutter view running |project|. + explicit FlutterWindow(RunLoop* run_loop, + const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The run loop driving events for this window. + RunLoop* run_loop_; + + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/main.cpp b/local_packages/extended_text_field-16.0.2/example/windows/runner/main.cpp new file mode 100644 index 0000000..fc17fec --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/runner/main.cpp @@ -0,0 +1,36 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "run_loop.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + RunLoop run_loop; + + flutter::DartProject project(L"data"); + FlutterWindow window(&run_loop, project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + run_loop.Run(); + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/resource.h b/local_packages/extended_text_field-16.0.2/example/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/resources/app_icon.ico b/local_packages/extended_text_field-16.0.2/example/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/local_packages/extended_text_field-16.0.2/example/windows/runner/resources/app_icon.ico differ diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/run_loop.cpp b/local_packages/extended_text_field-16.0.2/example/windows/runner/run_loop.cpp new file mode 100644 index 0000000..2d6636a --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/runner/run_loop.cpp @@ -0,0 +1,66 @@ +#include "run_loop.h" + +#include + +#include + +RunLoop::RunLoop() {} + +RunLoop::~RunLoop() {} + +void RunLoop::Run() { + bool keep_running = true; + TimePoint next_flutter_event_time = TimePoint::clock::now(); + while (keep_running) { + std::chrono::nanoseconds wait_duration = + std::max(std::chrono::nanoseconds(0), + next_flutter_event_time - TimePoint::clock::now()); + ::MsgWaitForMultipleObjects( + 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), + QS_ALLINPUT); + bool processed_events = false; + MSG message; + // All pending Windows messages must be processed; MsgWaitForMultipleObjects + // won't return again for items left in the queue after PeekMessage. + while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { + processed_events = true; + if (message.message == WM_QUIT) { + keep_running = false; + break; + } + ::TranslateMessage(&message); + ::DispatchMessage(&message); + // Allow Flutter to process messages each time a Windows message is + // processed, to prevent starvation. + next_flutter_event_time = + std::min(next_flutter_event_time, ProcessFlutterMessages()); + } + // If the PeekMessage loop didn't run, process Flutter messages. + if (!processed_events) { + next_flutter_event_time = + std::min(next_flutter_event_time, ProcessFlutterMessages()); + } + } +} + +void RunLoop::RegisterFlutterInstance( + flutter::FlutterEngine* flutter_instance) { + flutter_instances_.insert(flutter_instance); +} + +void RunLoop::UnregisterFlutterInstance( + flutter::FlutterEngine* flutter_instance) { + flutter_instances_.erase(flutter_instance); +} + +RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { + TimePoint next_event_time = TimePoint::max(); + for (auto instance : flutter_instances_) { + std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); + if (wait_duration != std::chrono::nanoseconds::max()) { + next_event_time = + std::min(next_event_time, TimePoint::clock::now() + wait_duration); + } + } + return next_event_time; +} diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/run_loop.h b/local_packages/extended_text_field-16.0.2/example/windows/runner/run_loop.h new file mode 100644 index 0000000..000d362 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/runner/run_loop.h @@ -0,0 +1,40 @@ +#ifndef RUNNER_RUN_LOOP_H_ +#define RUNNER_RUN_LOOP_H_ + +#include + +#include +#include + +// A runloop that will service events for Flutter instances as well +// as native messages. +class RunLoop { + public: + RunLoop(); + ~RunLoop(); + + // Prevent copying + RunLoop(RunLoop const&) = delete; + RunLoop& operator=(RunLoop const&) = delete; + + // Runs the run loop until the application quits. + void Run(); + + // Registers the given Flutter instance for event servicing. + void RegisterFlutterInstance( + flutter::FlutterEngine* flutter_instance); + + // Unregisters the given Flutter instance from event servicing. + void UnregisterFlutterInstance( + flutter::FlutterEngine* flutter_instance); + + private: + using TimePoint = std::chrono::steady_clock::time_point; + + // Processes all currently pending messages for registered Flutter instances. + TimePoint ProcessFlutterMessages(); + + std::set flutter_instances_; +}; + +#endif // RUNNER_RUN_LOOP_H_ diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/runner.exe.manifest b/local_packages/extended_text_field-16.0.2/example/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..c977c4a --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/utils.cpp b/local_packages/extended_text_field-16.0.2/example/windows/runner/utils.cpp new file mode 100644 index 0000000..37501e5 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/runner/utils.cpp @@ -0,0 +1,22 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/utils.h b/local_packages/extended_text_field-16.0.2/example/windows/runner/utils.h new file mode 100644 index 0000000..d792603 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/runner/utils.h @@ -0,0 +1,8 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +#endif // RUNNER_UTILS_H_ diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/win32_window.cpp b/local_packages/extended_text_field-16.0.2/example/windows/runner/win32_window.cpp new file mode 100644 index 0000000..efc3eb9 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/runner/win32_window.cpp @@ -0,0 +1,244 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/local_packages/extended_text_field-16.0.2/example/windows/runner/win32_window.h b/local_packages/extended_text_field-16.0.2/example/windows/runner/win32_window.h new file mode 100644 index 0000000..17ba431 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/example/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/local_packages/extended_text_field-16.0.2/lib/extended_text_field.dart b/local_packages/extended_text_field-16.0.2/lib/extended_text_field.dart new file mode 100644 index 0000000..a15ade0 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/extended_text_field.dart @@ -0,0 +1,9 @@ +library extended_text_field; + +export 'package:extended_text_library/extended_text_library.dart'; + +export 'src/extended/cupertino/spell_check_suggestions_toolbar.dart'; +export 'src/extended/material/spell_check_suggestions_toolbar.dart'; +export 'src/extended/widgets/text_field.dart'; +export 'src/keyboard/binding.dart'; +export 'src/keyboard/focus_node.dart'; diff --git a/local_packages/extended_text_field-16.0.2/lib/src/extended/cupertino/spell_check_suggestions_toolbar.dart b/local_packages/extended_text_field-16.0.2/lib/src/extended/cupertino/spell_check_suggestions_toolbar.dart new file mode 100644 index 0000000..49c585a --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/extended/cupertino/spell_check_suggestions_toolbar.dart @@ -0,0 +1,167 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:extended_text_field/src/extended/widgets/text_field.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart' + show SelectionChangedCause, SuggestionSpan; + +/// iOS only shows 3 spell check suggestions in the toolbar. +const int _kMaxSuggestions = 3; + +/// The default spell check suggestions toolbar for iOS. +/// +/// Tries to position itself below the [anchors], but if it doesn't fit, then it +/// readjusts to fit above bottom view insets. +/// +/// See also: +/// * [SpellCheckSuggestionsToolbar], which is similar but for both the +/// Material and Cupertino libraries. +/// [CupertinoSpellCheckSuggestionsToolbar] +class ExtendedCupertinoSpellCheckSuggestionsToolbar extends StatelessWidget { + /// Constructs a [ExtendedCupertinoSpellCheckSuggestionsToolbar]. + /// + /// [buttonItems] must not contain more than three items. + const ExtendedCupertinoSpellCheckSuggestionsToolbar({ + super.key, + required this.anchors, + required this.buttonItems, + }) : assert(buttonItems.length <= _kMaxSuggestions); + + /// Constructs a [ExtendedCupertinoSpellCheckSuggestionsToolbar] with the default + /// children for an [EditableText]. + /// + /// See also: + /// * [SpellCheckSuggestionsToolbar.editableText], which is similar but + /// builds an Android-style toolbar. + ExtendedCupertinoSpellCheckSuggestionsToolbar.editableText({ + super.key, + // zmtzawqlp + required ExtendedEditableTextState editableTextState, + }) : buttonItems = + buildButtonItems(editableTextState) ?? [], + anchors = editableTextState.contextMenuAnchors; + + /// The location on which to anchor the menu. + final TextSelectionToolbarAnchors anchors; + + /// The [ContextMenuButtonItem]s that will be turned into the correct button + /// widgets and displayed in the spell check suggestions toolbar. + /// + /// Must not contain more than three items. + /// + /// See also: + /// + /// * [AdaptiveTextSelectionToolbar.buttonItems], the list of + /// [ContextMenuButtonItem]s that are used to build the buttons of the + /// text selection toolbar. + /// * [SpellCheckSuggestionsToolbar.buttonItems], the list of + /// [ContextMenuButtonItem]s used to build the Material style spell check + /// suggestions toolbar. + final List buttonItems; + + /// Builds the button items for the toolbar based on the available + /// spell check suggestions. + static List? buildButtonItems( + ExtendedEditableTextState editableTextState, + ) { + // Determine if composing region is misspelled. + final SuggestionSpan? spanAtCursorIndex = + editableTextState.findSuggestionSpanAtCursorIndex( + editableTextState.currentTextEditingValue.selection.baseOffset, + ); + + if (spanAtCursorIndex == null) { + return null; + } + if (spanAtCursorIndex.suggestions.isEmpty) { + assert(debugCheckHasCupertinoLocalizations(editableTextState.context)); + final CupertinoLocalizations localizations = + CupertinoLocalizations.of(editableTextState.context); + return [ + ContextMenuButtonItem( + onPressed: null, + label: localizations.noSpellCheckReplacementsLabel, + ) + ]; + } + + final List buttonItems = []; + + // Build suggestion buttons. + for (final String suggestion + in spanAtCursorIndex.suggestions.take(_kMaxSuggestions)) { + buttonItems.add(ContextMenuButtonItem( + onPressed: () { + if (!editableTextState.mounted) { + return; + } + _replaceText( + editableTextState, + suggestion, + spanAtCursorIndex.range, + ); + }, + label: suggestion, + )); + } + return buttonItems; + } + + // zmtzawqlp + static void _replaceText(ExtendedEditableTextState editableTextState, + String text, TextRange replacementRange) { + // Replacement cannot be performed if the text is read only or obscured. + assert(!editableTextState.widget.readOnly && + !editableTextState.widget.obscureText); + + final TextEditingValue newValue = editableTextState.textEditingValue + .replaced( + replacementRange, + text, + ) + .copyWith( + selection: TextSelection.collapsed( + offset: replacementRange.start + text.length, + ), + ); + editableTextState.userUpdateTextEditingValue( + newValue, SelectionChangedCause.toolbar); + + // Schedule a call to bringIntoView() after renderEditable updates. + SchedulerBinding.instance.addPostFrameCallback((Duration duration) { + if (editableTextState.mounted) { + editableTextState + .bringIntoView(editableTextState.textEditingValue.selection.extent); + } + }, debugLabel: 'SpellCheckSuggestions.bringIntoView'); + editableTextState.hideToolbar(); + } + + /// Builds the toolbar buttons based on the [buttonItems]. + List _buildToolbarButtons(BuildContext context) { + return buttonItems.map((ContextMenuButtonItem buttonItem) { + return CupertinoTextSelectionToolbarButton.buttonItem( + buttonItem: buttonItem, + ); + }).toList(); + } + + @override + Widget build(BuildContext context) { + if (buttonItems.isEmpty) { + return const SizedBox.shrink(); + } + + final List children = _buildToolbarButtons(context); + return CupertinoTextSelectionToolbar( + anchorAbove: anchors.primaryAnchor, + anchorBelow: anchors.secondaryAnchor == null + ? anchors.primaryAnchor + : anchors.secondaryAnchor!, + children: children, + ); + } +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/extended/material/selectable_text.dart b/local_packages/extended_text_field-16.0.2/lib/src/extended/material/selectable_text.dart new file mode 100644 index 0000000..3b847b9 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/extended/material/selectable_text.dart @@ -0,0 +1,290 @@ +part of 'package:extended_text_field/src/extended/widgets/text_field.dart'; + +/// [SelectableText] +class ExtendedSelectableText extends _SelectableText { + const ExtendedSelectableText( + super.data, { + super.key, + super.focusNode, + super.style, + super.strutStyle, + super.textAlign, + super.textDirection, + super.textScaler, + super.showCursor = false, + super.autofocus = false, + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + super.toolbarOptions, + super.minLines, + super.maxLines, + super.cursorWidth = 2.0, + super.cursorHeight, + super.cursorRadius, + super.cursorColor, + super.selectionHeightStyle = ui.BoxHeightStyle.tight, + super.selectionWidthStyle = ui.BoxWidthStyle.tight, + super.dragStartBehavior = DragStartBehavior.start, + super.enableInteractiveSelection = true, + super.selectionControls, + super.onTap, + super.scrollPhysics, + super.semanticsLabel, + super.textHeightBehavior, + super.textWidthBasis, + super.onSelectionChanged, + // super.contextMenuBuilder = _defaultContextMenuBuilder, + this.extendedContextMenuBuilder = + ExtendedTextField._defaultContextMenuBuilder, + super.magnifierConfiguration, + this.specialTextSpanBuilder, + }); + + const ExtendedSelectableText.rich( + TextSpan textSpan, { + super.key, + super.focusNode, + super.style, + super.strutStyle, + super.textAlign, + super.textDirection, + super.textScaler, + super.showCursor = false, + super.autofocus = false, + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + super.toolbarOptions, + super.minLines, + super.maxLines, + super.cursorWidth = 2.0, + super.cursorHeight, + super.cursorRadius, + super.cursorColor, + super.selectionHeightStyle = ui.BoxHeightStyle.tight, + super.selectionWidthStyle = ui.BoxWidthStyle.tight, + super.dragStartBehavior = DragStartBehavior.start, + super.enableInteractiveSelection = true, + super.selectionControls, + super.onTap, + super.scrollPhysics, + super.semanticsLabel, + super.textHeightBehavior, + super.textWidthBasis, + super.onSelectionChanged, + // super.contextMenuBuilder = _defaultContextMenuBuilder, + this.extendedContextMenuBuilder = + ExtendedTextField._defaultContextMenuBuilder, + super.magnifierConfiguration, + this.specialTextSpanBuilder, + }) : super.rich(textSpan); + + /// build your ccustom text span + final SpecialTextSpanBuilder? specialTextSpanBuilder; + + /// {@template flutter.widgets.EditableText.contextMenuBuilder} + /// Builds the text selection toolbar when requested by the user. + /// + /// `primaryAnchor` is the desired anchor position for the context menu, while + /// `secondaryAnchor` is the fallback location if the menu doesn't fit. + /// + /// `buttonItems` represents the buttons that would be built by default for + /// this widget. + /// + /// {@tool dartpad} + /// This example shows how to customize the menu, in this case by keeping the + /// default buttons for the platform but modifying their appearance. + /// + /// ** See code in examples/api/lib/material/context_menu/editable_text_toolbar_builder.0.dart ** + /// {@end-tool} + /// + /// {@tool dartpad} + /// This example shows how to show a custom button only when an email address + /// is currently selected. + /// + /// ** See code in examples/api/lib/material/context_menu/editable_text_toolbar_builder.1.dart ** + /// {@end-tool} + /// + /// See also: + /// * [AdaptiveTextSelectionToolbar], which builds the default text selection + /// toolbar for the current platform, but allows customization of the + /// buttons. + /// * [AdaptiveTextSelectionToolbar.getAdaptiveButtons], which builds the + /// button Widgets for the current platform given + /// [ContextMenuButtonItem]s. + /// * [BrowserContextMenu], which allows the browser's context menu on web + /// to be disabled and Flutter-rendered context menus to appear. + /// {@endtemplate} + /// + /// If not provided, no context menu will be shown. + final ExtendedEditableTextContextMenuBuilder? extendedContextMenuBuilder; + + @override + State<_SelectableText> createState() => _ExtendedSelectableTextState(); +} + +class _ExtendedSelectableTextState extends _SelectableTextState { + ExtendedSelectableText get extendedSelectableText => + widget as ExtendedSelectableText; + @override + Widget build(BuildContext context) { + // TODO(garyq): Assert to block WidgetSpans from being used here are removed, + // but we still do not yet have nice handling of things like carets, clipboard, + // and other features. We should add proper support. Currently, caret handling + // is blocked on SkParagraph switch and https://github.com/flutter/engine/pull/27010 + // should be landed in SkParagraph after the switch is complete. + assert(debugCheckHasMediaQuery(context)); + assert(debugCheckHasDirectionality(context)); + assert( + !(widget.style != null && + !widget.style!.inherit && + (widget.style!.fontSize == null || + widget.style!.textBaseline == null)), + 'inherit false style must supply fontSize and textBaseline', + ); + + final ThemeData theme = Theme.of(context); + final DefaultSelectionStyle selectionStyle = + DefaultSelectionStyle.of(context); + final FocusNode focusNode = _effectiveFocusNode; + + TextSelectionControls? textSelectionControls = widget.selectionControls; + final bool paintCursorAboveText; + final bool cursorOpacityAnimates; + Offset? cursorOffset; + final Color cursorColor; + final Color selectionColor; + Radius? cursorRadius = widget.cursorRadius; + + switch (theme.platform) { + case TargetPlatform.iOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + forcePressEnabled = true; + textSelectionControls ??= cupertinoTextSelectionHandleControls; + paintCursorAboveText = true; + cursorOpacityAnimates = true; + cursorColor = widget.cursorColor ?? + selectionStyle.cursorColor ?? + cupertinoTheme.primaryColor; + selectionColor = selectionStyle.selectionColor ?? + cupertinoTheme.primaryColor.withOpacity(0.40); + cursorRadius ??= const Radius.circular(2.0); + cursorOffset = Offset( + iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0); + + case TargetPlatform.macOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + forcePressEnabled = false; + textSelectionControls ??= cupertinoDesktopTextSelectionHandleControls; + paintCursorAboveText = true; + cursorOpacityAnimates = true; + cursorColor = widget.cursorColor ?? + selectionStyle.cursorColor ?? + cupertinoTheme.primaryColor; + selectionColor = selectionStyle.selectionColor ?? + cupertinoTheme.primaryColor.withOpacity(0.40); + cursorRadius ??= const Radius.circular(2.0); + cursorOffset = Offset( + iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0); + + case TargetPlatform.android: + case TargetPlatform.fuchsia: + forcePressEnabled = false; + textSelectionControls ??= materialTextSelectionHandleControls; + paintCursorAboveText = false; + cursorOpacityAnimates = false; + cursorColor = widget.cursorColor ?? + selectionStyle.cursorColor ?? + theme.colorScheme.primary; + selectionColor = selectionStyle.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + + case TargetPlatform.linux: + case TargetPlatform.windows: + forcePressEnabled = false; + textSelectionControls ??= desktopTextSelectionHandleControls; + paintCursorAboveText = false; + cursorOpacityAnimates = false; + cursorColor = widget.cursorColor ?? + selectionStyle.cursorColor ?? + theme.colorScheme.primary; + selectionColor = selectionStyle.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + } + + final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context); + TextStyle? effectiveTextStyle = widget.style; + if (effectiveTextStyle == null || effectiveTextStyle.inherit) { + effectiveTextStyle = defaultTextStyle.style + .merge(widget.style ?? _controller._textSpan.style); + } + final Widget child = RepaintBoundary( + // zmtzawqlp + child: ExtendedEditableText( + key: editableTextKey, + style: effectiveTextStyle, + readOnly: true, + toolbarOptions: widget.toolbarOptions, + textWidthBasis: + widget.textWidthBasis ?? defaultTextStyle.textWidthBasis, + textHeightBehavior: + widget.textHeightBehavior ?? defaultTextStyle.textHeightBehavior, + showSelectionHandles: _showSelectionHandles, + showCursor: widget.showCursor, + controller: _controller, + focusNode: focusNode, + strutStyle: widget.strutStyle ?? const StrutStyle(), + textAlign: + widget.textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start, + textDirection: widget.textDirection, + textScaler: widget.textScaler, + autofocus: widget.autofocus, + forceLine: false, + minLines: widget.minLines, + maxLines: widget.maxLines ?? defaultTextStyle.maxLines, + selectionColor: selectionColor, + selectionControls: + widget.selectionEnabled ? textSelectionControls : null, + onSelectionChanged: _handleSelectionChanged, + onSelectionHandleTapped: _handleSelectionHandleTapped, + rendererIgnoresPointer: true, + cursorWidth: widget.cursorWidth, + cursorHeight: widget.cursorHeight, + cursorRadius: cursorRadius, + cursorColor: cursorColor, + selectionHeightStyle: widget.selectionHeightStyle, + selectionWidthStyle: widget.selectionWidthStyle, + cursorOpacityAnimates: cursorOpacityAnimates, + cursorOffset: cursorOffset, + paintCursorAboveText: paintCursorAboveText, + backgroundCursorColor: CupertinoColors.inactiveGray, + enableInteractiveSelection: widget.enableInteractiveSelection, + magnifierConfiguration: widget.magnifierConfiguration ?? + TextMagnifier.adaptiveMagnifierConfiguration, + dragStartBehavior: widget.dragStartBehavior, + scrollPhysics: widget.scrollPhysics, + autofillHints: null, + // zmtzawqlp + // contextMenuBuilder: widget.contextMenuBuilder, + extendedContextMenuBuilder: + extendedSelectableText.extendedContextMenuBuilder, + specialTextSpanBuilder: extendedSelectableText.specialTextSpanBuilder, + ), + ); + + return Semantics( + label: widget.semanticsLabel, + excludeSemantics: widget.semanticsLabel != null, + onLongPress: () { + _effectiveFocusNode.requestFocus(); + }, + child: _selectionGestureDetectorBuilder.buildGestureDetector( + behavior: HitTestBehavior.translucent, + child: child, + ), + ); + } +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/extended/material/spell_check_suggestions_toolbar.dart b/local_packages/extended_text_field-16.0.2/lib/src/extended/material/spell_check_suggestions_toolbar.dart new file mode 100644 index 0000000..39a7a7f --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/extended/material/spell_check_suggestions_toolbar.dart @@ -0,0 +1,269 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:extended_text_field/src/extended/widgets/text_field.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart' + show SelectionChangedCause, SuggestionSpan; + +// The default height of the SpellCheckSuggestionsToolbar, which +// assumes there are the maximum number of spell check suggestions available, 3. +// Size eyeballed on Pixel 4 emulator running Android API 31. +const double _kDefaultToolbarHeight = 193.0; + +/// The maximum number of suggestions in the toolbar is 3, plus a delete button. +const int _kMaxSuggestions = 3; + +/// The default spell check suggestions toolbar for Android. [SpellCheckSuggestionsToolbar] +/// +/// Tries to position itself below the [anchor], but if it doesn't fit, then it +/// readjusts to fit above bottom view insets. +/// +/// See also: +/// +/// * [CupertinoSpellCheckSuggestionsToolbar], which is similar but builds an +/// iOS-style spell check toolbar. +/// [SpellCheckSuggestionsToolbar] +class ExtendedSpellCheckSuggestionsToolbar extends StatelessWidget { + /// Constructs a [ExtendedSpellCheckSuggestionsToolbar]. + /// + /// [buttonItems] must not contain more than four items, generally three + /// suggestions and one delete button. + const ExtendedSpellCheckSuggestionsToolbar({ + super.key, + required this.anchor, + required this.buttonItems, + }) : assert(buttonItems.length <= _kMaxSuggestions + 1); + + /// Constructs a [ExtendedSpellCheckSuggestionsToolbar] with the default children for + /// an [EditableText]. + /// + /// See also: + /// * [CupertinoSpellCheckSuggestionsToolbar.editableText], which is similar + /// but builds an iOS-style toolbar. + ExtendedSpellCheckSuggestionsToolbar.editableText({ + super.key, + // zmtzawqlp + required ExtendedEditableTextState editableTextState, + }) : buttonItems = + buildButtonItems(editableTextState) ?? [], + anchor = getToolbarAnchor(editableTextState.contextMenuAnchors); + + /// {@template flutter.material.SpellCheckSuggestionsToolbar.anchor} + /// The focal point below which the toolbar attempts to position itself. + /// {@endtemplate} + final Offset anchor; + + /// The [ContextMenuButtonItem]s that will be turned into the correct button + /// widgets and displayed in the spell check suggestions toolbar. + /// + /// Must not contain more than four items, typically three suggestions and a + /// delete button. + /// + /// See also: + /// + /// * [AdaptiveTextSelectionToolbar.buttonItems], the list of + /// [ContextMenuButtonItem]s that are used to build the buttons of the + /// text selection toolbar. + /// * [CupertinoSpellCheckSuggestionsToolbar.buttonItems], the list of + /// [ContextMenuButtonItem]s used to build the Cupertino style spell check + /// suggestions toolbar. + final List buttonItems; + + /// Builds the button items for the toolbar based on the available + /// spell check suggestions. + static List? buildButtonItems( + ExtendedEditableTextState editableTextState, + ) { + // Determine if composing region is misspelled. + final SuggestionSpan? spanAtCursorIndex = + editableTextState.findSuggestionSpanAtCursorIndex( + editableTextState.currentTextEditingValue.selection.baseOffset, + ); + + if (spanAtCursorIndex == null) { + return null; + } + + final List buttonItems = []; + + // Build suggestion buttons. + for (final String suggestion + in spanAtCursorIndex.suggestions.take(_kMaxSuggestions)) { + buttonItems.add(ContextMenuButtonItem( + onPressed: () { + if (!editableTextState.mounted) { + return; + } + _replaceText( + editableTextState, + suggestion, + spanAtCursorIndex.range, + ); + }, + label: suggestion, + )); + } + + // Build delete button. + final ContextMenuButtonItem deleteButton = ContextMenuButtonItem( + onPressed: () { + if (!editableTextState.mounted) { + return; + } + _replaceText( + editableTextState, + '', + editableTextState.currentTextEditingValue.composing, + ); + }, + type: ContextMenuButtonType.delete, + ); + buttonItems.add(deleteButton); + + return buttonItems; + } + + // zmtzawqlp + static void _replaceText(ExtendedEditableTextState editableTextState, + String text, TextRange replacementRange) { + // Replacement cannot be performed if the text is read only or obscured. + assert(!editableTextState.widget.readOnly && + !editableTextState.widget.obscureText); + + final TextEditingValue newValue = + editableTextState.textEditingValue.replaced( + replacementRange, + text, + ); + editableTextState.userUpdateTextEditingValue( + newValue, SelectionChangedCause.toolbar); + + // Schedule a call to bringIntoView() after renderEditable updates. + SchedulerBinding.instance.addPostFrameCallback((Duration duration) { + if (editableTextState.mounted) { + editableTextState + .bringIntoView(editableTextState.textEditingValue.selection.extent); + } + }, debugLabel: 'SpellCheckerSuggestionsToolbar.bringIntoView'); + editableTextState.hideToolbar(); + } + + /// Determines the Offset that the toolbar will be anchored to. + static Offset getToolbarAnchor(TextSelectionToolbarAnchors anchors) { + // Since this will be positioned below the anchor point, use the secondary + // anchor by default. + return anchors.secondaryAnchor == null + ? anchors.primaryAnchor + : anchors.secondaryAnchor!; + } + + /// Builds the toolbar buttons based on the [buttonItems]. + List _buildToolbarButtons(BuildContext context) { + return buttonItems.map((ContextMenuButtonItem buttonItem) { + final TextSelectionToolbarTextButton button = + TextSelectionToolbarTextButton( + padding: const EdgeInsets.fromLTRB(20, 0, 0, 0), + onPressed: buttonItem.onPressed, + alignment: Alignment.centerLeft, + child: Text( + AdaptiveTextSelectionToolbar.getButtonLabel(context, buttonItem), + style: buttonItem.type == ContextMenuButtonType.delete + ? const TextStyle(color: Colors.blue) + : null, + ), + ); + + if (buttonItem.type != ContextMenuButtonType.delete) { + return button; + } + return DecoratedBox( + decoration: const BoxDecoration( + border: Border(top: BorderSide(color: Colors.grey))), + child: button, + ); + }).toList(); + } + + @override + Widget build(BuildContext context) { + if (buttonItems.isEmpty) { + return const SizedBox.shrink(); + } + + // Adjust toolbar height if needed. + final double spellCheckSuggestionsToolbarHeight = + _kDefaultToolbarHeight - (48.0 * (4 - buttonItems.length)); + // Incorporate the padding distance between the content and toolbar. + final MediaQueryData mediaQueryData = MediaQuery.of(context); + final double softKeyboardViewInsetsBottom = + mediaQueryData.viewInsets.bottom; + final double paddingAbove = mediaQueryData.padding.top + + CupertinoTextSelectionToolbar.kToolbarScreenPadding; + // Makes up for the Padding. + final Offset localAdjustment = Offset( + CupertinoTextSelectionToolbar.kToolbarScreenPadding, + paddingAbove, + ); + + return Padding( + padding: EdgeInsets.fromLTRB( + CupertinoTextSelectionToolbar.kToolbarScreenPadding, + paddingAbove, + CupertinoTextSelectionToolbar.kToolbarScreenPadding, + CupertinoTextSelectionToolbar.kToolbarScreenPadding + + softKeyboardViewInsetsBottom, + ), + child: CustomSingleChildLayout( + delegate: SpellCheckSuggestionsToolbarLayoutDelegate( + anchor: anchor - localAdjustment, + ), + child: AnimatedSize( + // This duration was eyeballed on a Pixel 2 emulator running Android + // API 28 for the Material TextSelectionToolbar. + duration: const Duration(milliseconds: 140), + child: _SpellCheckSuggestionsToolbarContainer( + height: spellCheckSuggestionsToolbarHeight, + children: [..._buildToolbarButtons(context)], + ), + ), + ), + ); + } +} + +/// The Material-styled toolbar outline for the spell check suggestions +/// toolbar. +class _SpellCheckSuggestionsToolbarContainer extends StatelessWidget { + const _SpellCheckSuggestionsToolbarContainer({ + required this.height, + required this.children, + }); + + final double height; + final List children; + + @override + Widget build(BuildContext context) { + return Material( + // This elevation was eyeballed on a Pixel 4 emulator running Android + // API 31 for the SpellCheckSuggestionsToolbar. + elevation: 2.0, + type: MaterialType.card, + child: SizedBox( + // This width was eyeballed on a Pixel 4 emulator running Android + // API 31 for the SpellCheckSuggestionsToolbar. + width: 165.0, + height: height, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: children, + ), + ), + ); + } +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/extended/rendering/editable.dart b/local_packages/extended_text_field-16.0.2/lib/src/extended/rendering/editable.dart new file mode 100644 index 0000000..c3de0c3 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/extended/rendering/editable.dart @@ -0,0 +1,199 @@ +part of 'package:extended_text_field/src/extended/widgets/text_field.dart'; + +/// [RenderEditable] +class ExtendedRenderEditable extends _RenderEditable { + ExtendedRenderEditable({ + super.text, + required super.textDirection, + super.textAlign = TextAlign.start, + super.cursorColor, + super.backgroundCursorColor, + super.showCursor, + super.hasFocus, + required super.startHandleLayerLink, + required super.endHandleLayerLink, + super.maxLines = 1, + super.minLines, + super.expands = false, + super.strutStyle, + super.selectionColor, + super.textScaler = TextScaler.noScaling, + super.selection, + required super.offset, + super.ignorePointer = false, + super.readOnly = false, + super.forceLine = true, + super.textHeightBehavior, + super.textWidthBasis = TextWidthBasis.parent, + super.obscuringCharacter = '•', + super.obscureText = false, + super.locale, + super.cursorWidth = 1.0, + super.cursorHeight, + super.cursorRadius, + super.paintCursorAboveText = false, + super.cursorOffset = Offset.zero, + super.devicePixelRatio = 1.0, + super.selectionHeightStyle = ui.BoxHeightStyle.tight, + super.selectionWidthStyle = ui.BoxWidthStyle.tight, + super.enableInteractiveSelection, + super.floatingCursorAddedMargin = const EdgeInsets.fromLTRB(4, 4, 4, 5), + super.promptRectRange, + super.promptRectColor, + super.clipBehavior = Clip.hardEdge, + required super.textSelectionDelegate, + super.painter, + super.foregroundPainter, + super.children, + this.supportSpecialText = false, + }) { + _findSpecialInlineSpanBase(text); + } + + bool supportSpecialText = false; + bool _hasSpecialInlineSpanBase = false; + bool get hasSpecialInlineSpanBase => + supportSpecialText && _hasSpecialInlineSpanBase; + + void _findSpecialInlineSpanBase(InlineSpan? span) { + _hasSpecialInlineSpanBase = false; + span?.visitChildren((InlineSpan span) { + if (span is SpecialInlineSpanBase) { + _hasSpecialInlineSpanBase = true; + return false; + } + return true; + }); + } + + @override + set text(InlineSpan? value) { + if (_textPainter.text == value) { + return; + } + _findSpecialInlineSpanBase(value); + super.text = value; + } + + @override + String get plainText { + return ExtendedTextLibraryUtils.textSpanToActualText(_textPainter.text!); + } + + /// Move the selection to the beginning or end of a word. + /// + /// {@macro flutter.rendering.RenderEditable.selectPosition} + @override + void selectWordEdge({required SelectionChangedCause cause}) { + _computeTextMetricsIfNeeded(); + assert(_lastTapDownPosition != null); + final TextPosition position = _textPainter.getPositionForOffset( + globalToLocal(_lastTapDownPosition!) - _paintOffset); + final TextRange word = _textPainter.getWordBoundary(position); + late TextSelection newSelection; + if (position.offset <= word.start) { + newSelection = TextSelection.collapsed(offset: word.start); + } else { + newSelection = TextSelection.collapsed( + offset: word.end, affinity: TextAffinity.upstream); + } + + /// zmtzawqlp + newSelection = hasSpecialInlineSpanBase + ? ExtendedTextLibraryUtils + .convertTextPainterSelectionToTextInputSelection( + text!, newSelection) + : newSelection; + _setSelection(newSelection, cause); + } + + @override + + /// Select text between the global positions [from] and [to]. + /// + /// [from] corresponds to the [TextSelection.baseOffset], and [to] corresponds + /// to the [TextSelection.extentOffset]. + void selectPositionAt( + {required Offset from, + Offset? to, + required SelectionChangedCause cause}) { + _computeTextMetricsIfNeeded(); + TextPosition fromPosition = + _textPainter.getPositionForOffset(globalToLocal(from) - _paintOffset); + TextPosition? toPosition = to == null + ? null + : _textPainter.getPositionForOffset(globalToLocal(to) - _paintOffset); + // zmtzawqlp + if (hasSpecialInlineSpanBase) { + fromPosition = + ExtendedTextLibraryUtils.convertTextPainterPostionToTextInputPostion( + text!, fromPosition)!; + toPosition = + ExtendedTextLibraryUtils.convertTextPainterPostionToTextInputPostion( + text!, toPosition); + } + final int baseOffset = fromPosition.offset; + final int extentOffset = toPosition?.offset ?? fromPosition.offset; + + final TextSelection newSelection = TextSelection( + baseOffset: baseOffset, + extentOffset: extentOffset, + affinity: fromPosition.affinity, + ); + + _setSelection(newSelection, cause); + } + + @override + TextSelection getWordAtOffset(TextPosition position) { + final TextSelection selection = super.getWordAtOffset(position); + + /// zmt + return hasSpecialInlineSpanBase + ? ExtendedTextLibraryUtils + .convertTextPainterSelectionToTextInputSelection(text!, selection, + selectWord: true) + : selection; + } + + @override + List getEndpointsForSelection(TextSelection selection) { + // zmtzawqlp + if (hasSpecialInlineSpanBase) { + selection = ExtendedTextLibraryUtils + .convertTextInputSelectionToTextPainterSelection(text!, selection); + } + + return super.getEndpointsForSelection(selection); + } + + @override + set selection(TextSelection? value) { + if (_selection == value) { + return; + } + _selection = value; + _selectionPainter.highlightedRange = getActualSelection(); + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + + @override + void setPromptRectRange(TextRange? newRange) { + _autocorrectHighlightPainter.highlightedRange = + getActualSelection(newRange: newRange); + } + + TextSelection? getActualSelection({TextRange? newRange}) { + TextSelection? value = selection; + if (newRange != null) { + value = + TextSelection(baseOffset: newRange.start, extentOffset: newRange.end); + } + + return hasSpecialInlineSpanBase + ? ExtendedTextLibraryUtils + .convertTextInputSelectionToTextPainterSelection(text!, value!) + : value; + } +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/extended/utils.dart b/local_packages/extended_text_field-16.0.2/lib/src/extended/utils.dart new file mode 100644 index 0000000..32b57a3 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/extended/utils.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +extension TextEditingControllerEx on TextEditingController { + /// Check that the [selection] is inside of the bounds of [text]. + bool isSelectionWithinTextBounds(TextSelection selection) { + return selection.start <= text.length && selection.end <= text.length; + } + + /// Check that the [selection] is inside of the composing range. + bool isSelectionWithinComposingRange(TextSelection selection) { + return selection.start >= value.composing.start && + selection.end <= value.composing.end; + } +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/extended/widgets/editable_text.dart b/local_packages/extended_text_field-16.0.2/lib/src/extended/widgets/editable_text.dart new file mode 100644 index 0000000..22a5a82 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/extended/widgets/editable_text.dart @@ -0,0 +1,1087 @@ +part of 'package:extended_text_field/src/extended/widgets/text_field.dart'; + +/// Signature for a widget builder that builds a context menu for the given +/// [EditableTextState]. +/// +/// See also: +/// +/// * [SelectableRegionContextMenuBuilder], which performs the same role for +/// [SelectableRegion]. +typedef ExtendedEditableTextContextMenuBuilder = Widget Function( + BuildContext context, + ExtendedEditableTextState editableTextState, +); + +/// [EditableText] +/// +class ExtendedEditableText extends _EditableText { + ExtendedEditableText({ + super.key, + required super.controller, + required super.focusNode, + super.readOnly = false, + super.obscuringCharacter = '•', + super.obscureText = false, + super.autocorrect = true, + super.smartDashesType, + super.smartQuotesType, + super.enableSuggestions = true, + required super.style, + super.strutStyle, + required super.cursorColor, + required super.backgroundCursorColor, + super.textAlign = TextAlign.start, + super.textDirection, + super.locale, + super.textScaler, + super.maxLines = 1, + super.minLines, + super.expands = false, + super.forceLine = true, + super.textHeightBehavior, + super.textWidthBasis = TextWidthBasis.parent, + super.autofocus = false, + super.showCursor, + super.showSelectionHandles = false, + super.selectionColor, + super.selectionControls, + super.keyboardType, + super.textInputAction, + super.textCapitalization = TextCapitalization.none, + super.onChanged, + super.onEditingComplete, + super.onSubmitted, + super.onAppPrivateCommand, + super.onSelectionChanged, + super.onSelectionHandleTapped, + super.groupId = EditableText, + super.onTapOutside, + super.inputFormatters, + super.mouseCursor, + super.rendererIgnoresPointer = false, + super.cursorWidth = 2.0, + super.cursorHeight, + super.cursorRadius, + super.cursorOpacityAnimates = false, + super.cursorOffset, + super.paintCursorAboveText = false, + super.selectionHeightStyle = ui.BoxHeightStyle.tight, + super.selectionWidthStyle = ui.BoxWidthStyle.tight, + super.scrollPadding = const EdgeInsets.all(20.0), + super.keyboardAppearance = Brightness.light, + super.dragStartBehavior = DragStartBehavior.start, + super.enableInteractiveSelection, + super.scrollController, + super.scrollPhysics, + super.autocorrectionTextRectColor, + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + ToolbarOptions? toolbarOptions, + super.autofillHints = const [], + super.autofillClient, + super.clipBehavior = Clip.hardEdge, + super.restorationId, + super.scrollBehavior, + super.scribbleEnabled = true, + super.enableIMEPersonalizedLearning = true, + super.contentInsertionConfiguration, + // super.contextMenuBuilder, + // super.spellCheckConfiguration, + this.extendedContextMenuBuilder, + this.extendedSpellCheckConfiguration, + super.magnifierConfiguration = TextMagnifierConfiguration.disabled, + super.undoController, + this.specialTextSpanBuilder, + }); + + /// build your ccustom text span + final SpecialTextSpanBuilder? specialTextSpanBuilder; + + /// {@template flutter.widgets.EditableText.contextMenuBuilder} + /// Builds the text selection toolbar when requested by the user. + /// + /// `primaryAnchor` is the desired anchor position for the context menu, while + /// `secondaryAnchor` is the fallback location if the menu doesn't fit. + /// + /// `buttonItems` represents the buttons that would be built by default for + /// this widget. + /// + /// {@tool dartpad} + /// This example shows how to customize the menu, in this case by keeping the + /// default buttons for the platform but modifying their appearance. + /// + /// ** See code in examples/api/lib/material/context_menu/editable_text_toolbar_builder.0.dart ** + /// {@end-tool} + /// + /// {@tool dartpad} + /// This example shows how to show a custom button only when an email address + /// is currently selected. + /// + /// ** See code in examples/api/lib/material/context_menu/editable_text_toolbar_builder.1.dart ** + /// {@end-tool} + /// + /// See also: + /// * [AdaptiveTextSelectionToolbar], which builds the default text selection + /// toolbar for the current platform, but allows customization of the + /// buttons. + /// * [AdaptiveTextSelectionToolbar.getAdaptiveButtons], which builds the + /// button Widgets for the current platform given + /// [ContextMenuButtonItem]s. + /// * [BrowserContextMenu], which allows the browser's context menu on web + /// to be disabled and Flutter-rendered context menus to appear. + /// {@endtemplate} + /// + /// If not provided, no context menu will be shown. + final ExtendedEditableTextContextMenuBuilder? extendedContextMenuBuilder; + + /// {@template flutter.widgets.EditableText.spellCheckConfiguration} + /// Configuration that details how spell check should be performed. + /// + /// Specifies the [SpellCheckService] used to spell check text input and the + /// [TextStyle] used to style text with misspelled words. + /// + /// If the [SpellCheckService] is left null, spell check is disabled by + /// default unless the [DefaultSpellCheckService] is supported, in which case + /// it is used. It is currently supported only on Android and iOS. + /// + /// If this configuration is left null, then spell check is disabled by default. + /// {@endtemplate} + final ExtendedSpellCheckConfiguration? extendedSpellCheckConfiguration; + @override + _EditableTextState createState() { + return ExtendedEditableTextState(); + } +} + +class ExtendedEditableTextState extends _EditableTextState { + ExtendedEditableText get extendedEditableText => + widget as ExtendedEditableText; + ExtendedSpellCheckConfiguration get extendedSpellCheckConfiguration => + _spellCheckConfiguration as ExtendedSpellCheckConfiguration; + + /// whether to support build SpecialText + bool get supportSpecialText => + extendedEditableText.specialTextSpanBuilder != null && + !widget.obscureText && + _textDirection == TextDirection.ltr; + + // State lifecycle: + + @override + void initState() { + super.initState(); + _spellCheckConfiguration = _inferSpellCheckConfiguration( + extendedEditableText.extendedSpellCheckConfiguration); + } + + /// Infers the [_SpellCheckConfiguration] used to perform spell check. + /// + /// If spell check is enabled, this will try to infer a value for + /// the [SpellCheckService] if left unspecified. + static _SpellCheckConfiguration _inferSpellCheckConfiguration( + ExtendedSpellCheckConfiguration? configuration) { + final SpellCheckService? spellCheckService = + configuration?.spellCheckService; + final bool spellCheckAutomaticallyDisabled = configuration == null || + configuration == const ExtendedSpellCheckConfiguration.disabled(); + final bool spellCheckServiceIsConfigured = spellCheckService != null || + spellCheckService == null && + WidgetsBinding + .instance.platformDispatcher.nativeSpellCheckServiceDefined; + if (spellCheckAutomaticallyDisabled || !spellCheckServiceIsConfigured) { + // Only enable spell check if a non-disabled configuration is provided + // and if that configuration does not specify a spell check service, + // a native spell checker must be supported. + assert(() { + if (!spellCheckAutomaticallyDisabled && + !spellCheckServiceIsConfigured) { + FlutterError.reportError( + FlutterErrorDetails( + exception: FlutterError( + 'Spell check was enabled with spellCheckConfiguration, but the ' + 'current platform does not have a supported spell check ' + 'service, and none was provided. Consider disabling spell ' + 'check for this platform or passing a SpellCheckConfiguration ' + 'with a specified spell check service.', + ), + library: 'widget library', + stack: StackTrace.current, + ), + ); + } + return true; + }()); + return const ExtendedSpellCheckConfiguration.disabled(); + } + + return configuration.copyWith( + spellCheckService: spellCheckService ?? DefaultSpellCheckService()); + } + + // zmtzawqlp + @override + Widget build(BuildContext context) { + assert(debugCheckHasMediaQuery(context)); + super.build(context); // See AutomaticKeepAliveClientMixin. + + final TextSelectionControls? controls = widget.selectionControls; + double? textScaleFactor; + final TextScaler effectiveTextScaler = switch (( + widget.textScaler, textScaleFactor //widget.textScaleFactor + )) { + (final TextScaler textScaler, _) => textScaler, + (null, final double textScaleFactor) => + TextScaler.linear(textScaleFactor), + (null, null) => MediaQuery.textScalerOf(context), + }; + + return _CompositionCallback( + compositeCallback: _compositeCallback, + enabled: _hasInputConnection, + child: TextFieldTapRegion( + groupId: widget.groupId, + onTapOutside: + _hasFocus ? widget.onTapOutside ?? _defaultOnTapOutside : null, + debugLabel: kReleaseMode ? null : 'ExtendedEditableText', + child: MouseRegion( + cursor: widget.mouseCursor ?? SystemMouseCursors.text, + child: Actions( + actions: _actions, + child: UndoHistory( + value: widget.controller, + onTriggered: (TextEditingValue value) { + userUpdateTextEditingValue( + value, SelectionChangedCause.keyboard); + }, + shouldChangeUndoStack: + (TextEditingValue? oldValue, TextEditingValue newValue) { + if (!newValue.selection.isValid) { + return false; + } + + if (oldValue == null) { + return true; + } + + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + // Composing text is not counted in history coalescing. + if (!widget.controller.value.composing.isCollapsed) { + return false; + } + case TargetPlatform.android: + // Gboard on Android puts non-CJK words in composing regions. Coalesce + // composing text in order to allow the saving of partial words in that + // case. + break; + } + + return oldValue.text != newValue.text || + oldValue.composing != newValue.composing; + }, + undoStackModifier: (TextEditingValue value) { + // On Android we should discard the composing region when pushing + // a new entry to the undo stack. This prevents the TextInputPlugin + // from restarting the input on every undo/redo when the composing + // region is changed by the framework. + return defaultTargetPlatform == TargetPlatform.android + ? value.copyWith(composing: TextRange.empty) + : value; + }, + focusNode: widget.focusNode, + controller: widget.undoController, + child: Focus( + focusNode: widget.focusNode, + includeSemantics: false, + debugLabel: kReleaseMode ? null : 'EditableText', + child: NotificationListener( + onNotification: (ScrollNotification notification) { + _handleContextMenuOnScroll(notification); + _scribbleCacheKey = null; + return false; + }, + child: Scrollable( + key: _scrollableKey, + excludeFromSemantics: true, + axisDirection: + _isMultiline ? AxisDirection.down : AxisDirection.right, + controller: _scrollController, + physics: widget.scrollPhysics, + dragStartBehavior: widget.dragStartBehavior, + restorationId: widget.restorationId, + // If a ScrollBehavior is not provided, only apply scrollbars when + // multiline. The overscroll indicator should not be applied in + // either case, glowing or stretching. + scrollBehavior: widget.scrollBehavior ?? + ScrollConfiguration.of(context).copyWith( + scrollbars: _isMultiline, + overscroll: false, + ), + viewportBuilder: + (BuildContext context, ViewportOffset offset) { + return CompositedTransformTarget( + link: _toolbarLayerLink, + child: Semantics( + onCopy: _semanticsOnCopy(controls), + onCut: _semanticsOnCut(controls), + onPaste: _semanticsOnPaste(controls), + child: _ScribbleFocusable( + focusNode: widget.focusNode, + editableKey: _editableKey, + enabled: widget.scribbleEnabled, + updateSelectionRects: () { + _openInputConnection(); + _updateSelectionRects(force: true); + }, + child: SizeChangedLayoutNotifier( + child: _ExtendedEditable( + key: _editableKey, + startHandleLayerLink: _startHandleLayerLink, + endHandleLayerLink: _endHandleLayerLink, + inlineSpan: buildTextSpan(), + value: _value, + cursorColor: _cursorColor, + backgroundCursorColor: + widget.backgroundCursorColor, + showCursor: _cursorVisibilityNotifier, + forceLine: widget.forceLine, + readOnly: widget.readOnly, + hasFocus: _hasFocus, + maxLines: widget.maxLines, + minLines: widget.minLines, + expands: widget.expands, + strutStyle: widget.strutStyle, + selectionColor: _selectionOverlay + ?.spellCheckToolbarIsVisible ?? + false + ? _spellCheckConfiguration + .misspelledSelectionColor ?? + widget.selectionColor + : widget.selectionColor, + textScaler: effectiveTextScaler, + textAlign: widget.textAlign, + textDirection: _textDirection, + locale: widget.locale, + textHeightBehavior: widget.textHeightBehavior ?? + DefaultTextHeightBehavior.maybeOf(context), + textWidthBasis: widget.textWidthBasis, + obscuringCharacter: widget.obscuringCharacter, + obscureText: widget.obscureText, + offset: offset, + rendererIgnoresPointer: + widget.rendererIgnoresPointer, + cursorWidth: widget.cursorWidth, + cursorHeight: widget.cursorHeight, + cursorRadius: widget.cursorRadius, + cursorOffset: + widget.cursorOffset ?? Offset.zero, + selectionHeightStyle: + widget.selectionHeightStyle, + selectionWidthStyle: widget.selectionWidthStyle, + paintCursorAboveText: + widget.paintCursorAboveText, + enableInteractiveSelection: + widget._userSelectionEnabled, + textSelectionDelegate: this, + devicePixelRatio: _devicePixelRatio, + promptRectRange: _currentPromptRectRange, + promptRectColor: + widget.autocorrectionTextRectColor, + clipBehavior: widget.clipBehavior, + supportSpecialText: supportSpecialText, + ), + ), + ), + ), + ); + }, + ), + ), + ), + ), + ), + ), + ), + ); + } + + /// Shows toolbar with spell check suggestions of misspelled words that are + /// available for click-and-replace. + @override + bool showSpellCheckSuggestionsToolbar() { + // Spell check suggestions toolbars are intended to be shown on non-web + // platforms. Additionally, the Cupertino style toolbar can't be drawn on + // the web with the HTML renderer due to + // https://github.com/flutter/flutter/issues/123560. + + if (!spellCheckEnabled || + _webContextMenuEnabled || + widget.readOnly || + _selectionOverlay == null || + !_spellCheckResultsReceived || + findSuggestionSpanAtCursorIndex( + textEditingValue.selection.extentOffset) == + null) { + // Only attempt to show the spell check suggestions toolbar if there + // is a toolbar specified and spell check suggestions available to show. + return false; + } + + assert( + _spellCheckConfiguration.spellCheckSuggestionsToolbarBuilder != null, + 'spellCheckSuggestionsToolbarBuilder must be defined in ' + 'SpellCheckConfiguration to show a toolbar with spell check ' + 'suggestions', + ); + + // zmtzawqlp + _selectionOverlay!.showSpellCheckSuggestionsToolbar( + (BuildContext context) { + // zmtzawqlp + return extendedSpellCheckConfiguration + .extendedSpellCheckSuggestionsToolbarBuilder!( + context, + this, + ); + }, + ); + return true; + } + + @override + _TextSelectionOverlay _createSelectionOverlay() { + final ExtendedTextSelectionOverlay selectionOverlay = + ExtendedTextSelectionOverlay( + clipboardStatus: clipboardStatus, + context: context, + value: _value, + debugRequiredFor: widget, + toolbarLayerLink: _toolbarLayerLink, + startHandleLayerLink: _startHandleLayerLink, + endHandleLayerLink: _endHandleLayerLink, + renderObject: renderEditable, + selectionControls: widget.selectionControls, + selectionDelegate: this, + dragStartBehavior: widget.dragStartBehavior, + onSelectionHandleTapped: widget.onSelectionHandleTapped, + // zmtzawqlp + contextMenuBuilder: + extendedEditableText.extendedContextMenuBuilder == null || + _webContextMenuEnabled + ? null + : (BuildContext context) { + return extendedEditableText.extendedContextMenuBuilder!( + context, + this, + ); + }, + magnifierConfiguration: widget.magnifierConfiguration, + ); + + return selectionOverlay; + } + + /// Builds [TextSpan] from current editing value. + /// + /// By default makes text in composing range appear as underlined. + /// Descendants can override this method to customize appearance of text. + @override + TextSpan buildTextSpan() { + if (widget.obscureText) { + String text = _value.text; + text = widget.obscuringCharacter * text.length; + // Reveal the latest character in an obscured field only on mobile. + // Newer versions of iOS (iOS 15+) no longer reveal the most recently + // entered character. + const Set mobilePlatforms = { + TargetPlatform.android, + TargetPlatform.fuchsia, + }; + final bool brieflyShowPassword = + WidgetsBinding.instance.platformDispatcher.brieflyShowPassword && + mobilePlatforms.contains(defaultTargetPlatform); + if (brieflyShowPassword) { + final int? o = + _obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null; + if (o != null && o >= 0 && o < text.length) { + text = text.replaceRange(o, o + 1, _value.text.substring(o, o + 1)); + } + } + return TextSpan(style: _style, text: text); + } + + // zmtzawqlp + if (_value.composing.isValid && !widget.readOnly) { + final TextStyle composingStyle = widget.style.merge( + const TextStyle(decoration: TextDecoration.underline), + ); + final String beforeText = _value.composing.textBefore(_value.text); + final String insideText = _value.composing.textInside(_value.text); + final String afterText = _value.composing.textAfter(_value.text); + + if (supportSpecialText) { + final TextSpan before = extendedEditableText.specialTextSpanBuilder! + .build(beforeText, textStyle: widget.style); + final TextSpan after = extendedEditableText.specialTextSpanBuilder! + .build(afterText, textStyle: widget.style); + + final List children = []; + + children.add(before); + + children.add(TextSpan( + style: composingStyle, + text: insideText, + )); + + children.add(after); + + return TextSpan(style: widget.style, children: children); + } + + return TextSpan(style: widget.style, children: [ + TextSpan(text: beforeText), + TextSpan( + style: composingStyle, + text: insideText, + ), + TextSpan(text: afterText), + ]); + } + + if (supportSpecialText) { + final TextSpan? specialTextSpan = extendedEditableText + .specialTextSpanBuilder + ?.build(_value.text, textStyle: widget.style); + if (specialTextSpan != null) { + return specialTextSpan; + } + } + + if (_placeholderLocation >= 0 && + _placeholderLocation <= _value.text.length) { + final List<_ScribblePlaceholder> placeholders = <_ScribblePlaceholder>[]; + final int placeholderLocation = _value.text.length - _placeholderLocation; + if (_isMultiline) { + // The zero size placeholder here allows the line to break and keep the caret on the first line. + placeholders.add(const _ScribblePlaceholder( + child: SizedBox.shrink(), size: Size.zero)); + placeholders.add(_ScribblePlaceholder( + child: const SizedBox.shrink(), + size: Size(renderEditable.size.width, 0.0))); + } else { + placeholders.add(const _ScribblePlaceholder( + child: SizedBox.shrink(), size: Size(100.0, 0.0))); + } + return TextSpan( + style: _style, + children: [ + TextSpan(text: _value.text.substring(0, placeholderLocation)), + ...placeholders, + TextSpan(text: _value.text.substring(placeholderLocation)), + ], + ); + } + final bool withComposing = !widget.readOnly && _hasFocus; + if (_spellCheckResultsReceived) { + // If the composing range is out of range for the current text, ignore it to + // preserve the tree integrity, otherwise in release mode a RangeError will + // be thrown and this EditableText will be built with a broken subtree. + assert(!_value.composing.isValid || + !withComposing || + _value.isComposingRangeValid); + + final bool composingRegionOutOfRange = + !_value.isComposingRangeValid || !withComposing; + + return buildTextSpanWithSpellCheckSuggestions( + _value, + composingRegionOutOfRange, + _style, + _spellCheckConfiguration.misspelledTextStyle!, + spellCheckResults!, + ); + } + + // Read only mode should not paint text composing. + return widget.controller.buildTextSpan( + context: context, + style: _style, + withComposing: withComposing, + ); + } + + @override + void bringIntoView(TextPosition position, {double offset = 0}) { + // zmtzawqlp + if (supportSpecialText) { + position = + ExtendedTextLibraryUtils.convertTextInputPostionToTextPainterPostion( + renderEditable.text!, + position, + ); + } + final Rect localRect = renderEditable.getLocalRectForCaret(position); + final RevealedOffset targetOffset = _getOffsetToRevealCaret(localRect); + + // zmtzawqlp + _scrollController.jumpTo(targetOffset.offset + offset); + renderEditable.showOnScreen(rect: targetOffset.rect); + } + + ///zmt + TextEditingValue _handleSpecialTextSpan(TextEditingValue value) { + if (supportSpecialText) { + final bool textChanged = _value.text != value.text; + final bool selectionChanged = _value.selection != value.selection; + if (textChanged) { + final TextSpan newTextSpan = extendedEditableText + .specialTextSpanBuilder! + .build(value.text, textStyle: widget.style); + + final TextSpan oldTextSpan = extendedEditableText + .specialTextSpanBuilder! + .build(_value.text, textStyle: widget.style); + value = ExtendedTextLibraryUtils.handleSpecialTextSpanDelete( + value, _value, oldTextSpan, _textInputConnection); + + final String text = newTextSpan.toPlainText(); + //correct caret Offset + //make sure caret is not in text when caretIn is false + if (text != value.text || selectionChanged) { + value = ExtendedTextLibraryUtils.correctCaretOffset( + value, + newTextSpan, + _textInputConnection, + ); + } + } else if (selectionChanged) { + late final InlineSpan inlineSpan; + + // after pinying complete, the _ExtendedEditable.inlineSpan is not the same as _value.text + // #255 + // only for windows + if (defaultTargetPlatform == TargetPlatform.windows && + // correct caret offset, pinying complete + !value.composing.isValid && + _value.composing.isValid) { + inlineSpan = extendedEditableText.specialTextSpanBuilder! + .build(_value.text, textStyle: widget.style); + _value = _value.copyWith(selection: value.selection); + } else { + inlineSpan = + (_editableKey.currentWidget as _ExtendedEditable).inlineSpan; + } + + value = ExtendedTextLibraryUtils.correctCaretOffset( + value, + inlineSpan, + _textInputConnection, + oldValue: _value, + ); + } + } + + return value; + } + + @override + void updateEditingValue(TextEditingValue value) { + // This method handles text editing state updates from the platform text + // input plugin. The [EditableText] may not have the focus or an open input + // connection, as autofill can update a disconnected [EditableText]. + + // Since we still have to support keyboard select, this is the best place + // to disable text updating. + if (!_shouldCreateInputConnection) { + return; + } + + if (_checkNeedsAdjustAffinity(value)) { + value = value.copyWith( + selection: + value.selection.copyWith(affinity: _value.selection.affinity)); + } + + if (widget.readOnly) { + // In the read-only case, we only care about selection changes, and reject + // everything else. + value = _value.copyWith(selection: value.selection); + } + _lastKnownRemoteTextEditingValue = value; + // zmtzawqlp + value = _handleSpecialTextSpan(value); + if (value == _value) { + // This is possible, for example, when the numeric keyboard is input, + // the engine will notify twice for the same value. + // Track at https://github.com/flutter/flutter/issues/65811 + return; + } + + if (value.text == _value.text && value.composing == _value.composing) { + // `selection` is the only change. + SelectionChangedCause cause; + if (_textInputConnection?.scribbleInProgress ?? false) { + cause = SelectionChangedCause.scribble; + } else if (_pointOffsetOrigin != null) { + cause = SelectionChangedCause.forcePress; + } else { + cause = SelectionChangedCause.keyboard; + } + _handleSelectionChanged(value.selection, cause); + } else { + if (value.text != _value.text) { + // Hide the toolbar if the text was changed, but only hide the toolbar + // overlay; the selection handle's visibility will be handled + // by `_handleSelectionChanged`. https://github.com/flutter/flutter/issues/108673 + hideToolbar(false); + } + _currentPromptRectRange = null; + + final bool revealObscuredInput = _hasInputConnection && + widget.obscureText && + WidgetsBinding.instance.platformDispatcher.brieflyShowPassword && + value.text.length == _value.text.length + 1; + + _obscureShowCharTicksPending = + revealObscuredInput ? _kObscureShowLatestCharCursorTicks : 0; + _obscureLatestCharIndex = + revealObscuredInput ? _value.selection.baseOffset : null; + _formatAndSetValue(value, SelectionChangedCause.keyboard); + } + + // Wherever the value is changed by the user, schedule a showCaretOnScreen + // to make sure the user can see the changes they just made. Programmatic + // changes to `textEditingValue` do not trigger the behavior even if the + // text field is focused. + _scheduleShowCaretOnScreen(withAnimation: true); + if (_hasInputConnection) { + // To keep the cursor from blinking while typing, we want to restart the + // cursor timer every time a new character is typed. + _stopCursorBlink(resetCharTicks: false); + _startCursorBlink(); + } + } + + @override + void userUpdateTextEditingValue( + TextEditingValue value, SelectionChangedCause? cause) { + // zmtzawqlp + value = _handleSpecialTextSpan(value); + // Compare the current TextEditingValue with the pre-format new + // TextEditingValue value, in case the formatter would reject the change. + final bool shouldShowCaret = + widget.readOnly ? _value.selection != value.selection : _value != value; + if (shouldShowCaret) { + _scheduleShowCaretOnScreen(withAnimation: true); + } + + // Even if the value doesn't change, it may be necessary to focus and build + // the selection overlay. For example, this happens when right clicking an + // unfocused field that previously had a selection in the same spot. + if (value == textEditingValue) { + if (!widget.focusNode.hasFocus) { + _flagInternalFocus(); + widget.focusNode.requestFocus(); + _selectionOverlay ??= _createSelectionOverlay(); + } + return; + } + + _formatAndSetValue(value, cause, userInteraction: true); + } + + @override + void updateFloatingCursor(RawFloatingCursorPoint point) { + _floatingCursorResetController ??= AnimationController( + vsync: this, + )..addListener(_onFloatingCursorResetTick); + switch (point.state) { + case FloatingCursorDragState.Start: + if (_floatingCursorResetController!.isAnimating) { + _floatingCursorResetController!.stop(); + _onFloatingCursorResetTick(); + } + // Stop cursor blinking and making it visible. + _stopCursorBlink(resetCharTicks: false); + _cursorBlinkOpacityController.value = 1.0; + // We want to send in points that are centered around a (0,0) origin, so + // we cache the position. + _pointOffsetOrigin = point.offset; + + final Offset startCaretCenter; + final TextPosition currentTextPosition; + final bool shouldResetOrigin; + // Only non-null when starting a floating cursor via long press. + if (point.startLocation != null) { + shouldResetOrigin = false; + (startCaretCenter, currentTextPosition) = point.startLocation!; + } else { + shouldResetOrigin = true; + // zmtzawqlp + currentTextPosition = supportSpecialText + ? ExtendedTextLibraryUtils + .convertTextInputPostionToTextPainterPostion( + renderEditable.text!, + renderEditable.selection!.base, + ) + : TextPosition( + offset: renderEditable.selection!.baseOffset, + affinity: renderEditable.selection!.affinity); + startCaretCenter = + renderEditable.getLocalRectForCaret(currentTextPosition).center; + } + + _startCaretCenter = startCaretCenter; + _lastBoundedOffset = + renderEditable.calculateBoundedFloatingCursorOffset( + _startCaretCenter! - _floatingCursorOffset, + shouldResetOrigin: shouldResetOrigin); + _lastTextPosition = currentTextPosition; + renderEditable.setFloatingCursor( + point.state, _lastBoundedOffset!, _lastTextPosition!); + case FloatingCursorDragState.Update: + final Offset centeredPoint = point.offset! - _pointOffsetOrigin!; + final Offset rawCursorOffset = + _startCaretCenter! + centeredPoint - _floatingCursorOffset; + + _lastBoundedOffset = renderEditable + .calculateBoundedFloatingCursorOffset(rawCursorOffset); + _lastTextPosition = renderEditable.getPositionForPoint(renderEditable + .localToGlobal(_lastBoundedOffset! + _floatingCursorOffset)); + // zmtzawlp + if (supportSpecialText) { + _lastTextPosition = + ExtendedTextLibraryUtils.makeSureCaretNotInSpecialText( + renderEditable.text!, _lastTextPosition!); + } + + renderEditable.setFloatingCursor( + point.state, _lastBoundedOffset!, _lastTextPosition!); + case FloatingCursorDragState.End: + // Resume cursor blinking. + _startCursorBlink(); + // We skip animation if no update has happened. + if (_lastTextPosition != null && _lastBoundedOffset != null) { + _floatingCursorResetController!.value = 0.0; + _floatingCursorResetController!.animateTo(1.0, + // zmtzawqlp + duration: _EditableTextState._floatingCursorResetTime, + curve: Curves.decelerate); + } + } + } + + @override + void _scheduleShowCaretOnScreen({required bool withAnimation}) { + if (_showCaretOnScreenScheduled) { + return; + } + _showCaretOnScreenScheduled = true; + SchedulerBinding.instance.addPostFrameCallback((Duration _) { + _showCaretOnScreenScheduled = false; + // Since we are in a post frame callback, check currentContext in case + // RenderEditable has been disposed (in which case it will be null). + final _RenderEditable? renderEditable = + _editableKey.currentContext?.findRenderObject() as _RenderEditable?; + if (renderEditable == null || + !(renderEditable.selection?.isValid ?? false) || + !_scrollController.hasClients) { + return; + } + + final double lineHeight = renderEditable.preferredLineHeight; + + // Enlarge the target rect by scrollPadding to ensure that caret is not + // positioned directly at the edge after scrolling. + double bottomSpacing = widget.scrollPadding.bottom; + if (_selectionOverlay?.selectionControls != null) { + final double handleHeight = _selectionOverlay!.selectionControls! + .getHandleSize(lineHeight) + .height; + final double interactiveHandleHeight = math.max( + handleHeight, + kMinInteractiveDimension, + ); + final Offset anchor = + _selectionOverlay!.selectionControls!.getHandleAnchor( + TextSelectionHandleType.collapsed, + lineHeight, + ); + final double handleCenter = handleHeight / 2 - anchor.dy; + bottomSpacing = math.max( + handleCenter + interactiveHandleHeight / 2, + bottomSpacing, + ); + } + + final EdgeInsets caretPadding = + widget.scrollPadding.copyWith(bottom: bottomSpacing); + + final Rect caretRect = renderEditable.getLocalRectForCaret( + // renderEditable.selection + // zmtzawqlp + (renderEditable as ExtendedRenderEditable).getActualSelection()!.extent, + ); + final RevealedOffset targetOffset = _getOffsetToRevealCaret(caretRect); + + final Rect rectToReveal; + final TextSelection selection = textEditingValue.selection; + if (selection.isCollapsed) { + rectToReveal = targetOffset.rect; + } else { + final List selectionBoxes = + renderEditable.getBoxesForSelection(selection); + // selectionBoxes may be empty if, for example, the selection does not + // encompass a full character, like if it only contained part of an + // extended grapheme cluster. + if (selectionBoxes.isEmpty) { + rectToReveal = targetOffset.rect; + } else { + rectToReveal = selection.baseOffset < selection.extentOffset + ? selectionBoxes.last.toRect() + : selectionBoxes.first.toRect(); + } + } + + if (withAnimation) { + _scrollController.animateTo( + targetOffset.offset, + duration: _EditableTextState._caretAnimationDuration, + curve: _EditableTextState._caretAnimationCurve, + ); + renderEditable.showOnScreen( + rect: caretPadding.inflateRect(rectToReveal), + duration: _EditableTextState._caretAnimationDuration, + curve: _EditableTextState._caretAnimationCurve, + ); + } else { + _scrollController.jumpTo(targetOffset.offset); + renderEditable.showOnScreen( + rect: caretPadding.inflateRect(rectToReveal), + ); + } + }); + } + + @override + void _updateCaretRectIfNeeded() { + // zmtzawqlp + final TextSelection? selection = // renderEditable.selection; + (renderEditable as ExtendedRenderEditable).getActualSelection(); + if (selection == null || !selection.isValid || !selection.isCollapsed) { + return; + } + final TextPosition currentTextPosition = + TextPosition(offset: selection.start); + final Rect caretRect = + renderEditable.getLocalRectForCaret(currentTextPosition); + _textInputConnection!.setCaretRect(caretRect); + } +} + +class _ExtendedEditable extends _Editable { + _ExtendedEditable({ + super.key, + required super.inlineSpan, + required super.value, + required super.startHandleLayerLink, + required super.endHandleLayerLink, + super.cursorColor, + super.backgroundCursorColor, + required super.showCursor, + required super.forceLine, + required super.readOnly, + super.textHeightBehavior, + required super.textWidthBasis, + required super.hasFocus, + required super.maxLines, + super.minLines, + required super.expands, + super.strutStyle, + super.selectionColor, + required super.textScaler, + required super.textAlign, + required super.textDirection, + super.locale, + required super.obscuringCharacter, + required super.obscureText, + required super.offset, + super.rendererIgnoresPointer = false, + required super.cursorWidth, + super.cursorHeight, + super.cursorRadius, + required super.cursorOffset, + required super.paintCursorAboveText, + super.selectionHeightStyle = ui.BoxHeightStyle.tight, + super.selectionWidthStyle = ui.BoxWidthStyle.tight, + super.enableInteractiveSelection = true, + required super.textSelectionDelegate, + required super.devicePixelRatio, + super.promptRectRange, + super.promptRectColor, + required super.clipBehavior, + this.supportSpecialText = false, + }); + + final bool supportSpecialText; + + @override + ExtendedRenderEditable createRenderObject(BuildContext context) { + return ExtendedRenderEditable( + text: inlineSpan, + cursorColor: cursorColor, + startHandleLayerLink: startHandleLayerLink, + endHandleLayerLink: endHandleLayerLink, + backgroundCursorColor: backgroundCursorColor, + showCursor: showCursor, + forceLine: forceLine, + readOnly: readOnly, + hasFocus: hasFocus, + maxLines: maxLines, + minLines: minLines, + expands: expands, + strutStyle: strutStyle, + selectionColor: selectionColor, + textScaler: textScaler, + textAlign: textAlign, + textDirection: textDirection, + locale: locale ?? Localizations.maybeLocaleOf(context), + selection: value.selection, + offset: offset, + ignorePointer: rendererIgnoresPointer, + obscuringCharacter: obscuringCharacter, + obscureText: obscureText, + textHeightBehavior: textHeightBehavior, + textWidthBasis: textWidthBasis, + cursorWidth: cursorWidth, + cursorHeight: cursorHeight, + cursorRadius: cursorRadius, + cursorOffset: cursorOffset, + paintCursorAboveText: paintCursorAboveText, + selectionHeightStyle: selectionHeightStyle, + selectionWidthStyle: selectionWidthStyle, + enableInteractiveSelection: enableInteractiveSelection, + textSelectionDelegate: textSelectionDelegate, + devicePixelRatio: devicePixelRatio, + promptRectRange: promptRectRange, + promptRectColor: promptRectColor, + clipBehavior: clipBehavior, + supportSpecialText: supportSpecialText, + ); + } + + // zmtzawqlp + @override + void updateRenderObject(BuildContext context, _RenderEditable renderObject) { + super.updateRenderObject(context, renderObject); + (renderObject as ExtendedRenderEditable).supportSpecialText = + supportSpecialText; + } +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/extended/widgets/spell_check.dart b/local_packages/extended_text_field-16.0.2/lib/src/extended/widgets/spell_check.dart new file mode 100644 index 0000000..c9d8a2e --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/extended/widgets/spell_check.dart @@ -0,0 +1,111 @@ +part of 'package:extended_text_field/src/extended/widgets/text_field.dart'; + +/// [SpellCheckConfiguration] +class ExtendedSpellCheckConfiguration extends _SpellCheckConfiguration { + /// Creates a configuration that specifies the service and suggestions handler + /// for spell check. + const ExtendedSpellCheckConfiguration({ + super.spellCheckService, + super.misspelledSelectionColor, + super.misspelledTextStyle, + // super.spellCheckSuggestionsToolbarBuilder, + this.extendedSpellCheckSuggestionsToolbarBuilder, + }); + + const ExtendedSpellCheckConfiguration.disabled() + : extendedSpellCheckSuggestionsToolbarBuilder = null, + super.disabled(); + + /// {@template flutter.widgets.EditableText.contextMenuBuilder} + /// Builds the text selection toolbar when requested by the user. + /// + /// `primaryAnchor` is the desired anchor position for the context menu, while + /// `secondaryAnchor` is the fallback location if the menu doesn't fit. + /// + /// `buttonItems` represents the buttons that would be built by default for + /// this widget. + /// + /// {@tool dartpad} + /// This example shows how to customize the menu, in this case by keeping the + /// default buttons for the platform but modifying their appearance. + /// + /// ** See code in examples/api/lib/material/context_menu/editable_text_toolbar_builder.0.dart ** + /// {@end-tool} + /// + /// {@tool dartpad} + /// This example shows how to show a custom button only when an email address + /// is currently selected. + /// + /// ** See code in examples/api/lib/material/context_menu/editable_text_toolbar_builder.1.dart ** + /// {@end-tool} + /// + /// See also: + /// * [AdaptiveTextSelectionToolbar], which builds the default text selection + /// toolbar for the current platform, but allows customization of the + /// buttons. + /// * [AdaptiveTextSelectionToolbar.getAdaptiveButtons], which builds the + /// button Widgets for the current platform given + /// [ContextMenuButtonItem]s. + /// * [BrowserContextMenu], which allows the browser's context menu on web + /// to be disabled and Flutter-rendered context menus to appear. + /// {@endtemplate} + /// + /// If not provided, no context menu will be shown. + final ExtendedEditableTextContextMenuBuilder? + extendedSpellCheckSuggestionsToolbarBuilder; + + /// Returns a copy of the current [_SpellCheckConfiguration] instance with + /// specified overrides. + @override + _SpellCheckConfiguration copyWith({ + SpellCheckService? spellCheckService, + Color? misspelledSelectionColor, + TextStyle? misspelledTextStyle, + EditableTextContextMenuBuilder? spellCheckSuggestionsToolbarBuilder, + ExtendedEditableTextContextMenuBuilder? + extendedSpellCheckSuggestionsToolbarBuilder, + }) { + if (!_spellCheckEnabled) { + // A new configuration should be constructed to enable spell check. + return const _SpellCheckConfiguration.disabled(); + } + + return ExtendedSpellCheckConfiguration( + spellCheckService: spellCheckService ?? this.spellCheckService, + misspelledSelectionColor: + misspelledSelectionColor ?? this.misspelledSelectionColor, + misspelledTextStyle: misspelledTextStyle ?? this.misspelledTextStyle, + extendedSpellCheckSuggestionsToolbarBuilder: + extendedSpellCheckSuggestionsToolbarBuilder ?? + this.extendedSpellCheckSuggestionsToolbarBuilder, + // spellCheckSuggestionsToolbarBuilder: + // spellCheckSuggestionsToolbarBuilder ?? + // this.spellCheckSuggestionsToolbarBuilder, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + return other is ExtendedSpellCheckConfiguration && + other.spellCheckService == spellCheckService && + other.misspelledTextStyle == misspelledTextStyle && + other.spellCheckSuggestionsToolbarBuilder == + spellCheckSuggestionsToolbarBuilder && + other._spellCheckEnabled == _spellCheckEnabled && + other.extendedSpellCheckSuggestionsToolbarBuilder == + extendedSpellCheckSuggestionsToolbarBuilder; + } + + @override + int get hashCode => Object.hash( + spellCheckService, + misspelledTextStyle, + spellCheckSuggestionsToolbarBuilder, + _spellCheckEnabled, + extendedSpellCheckSuggestionsToolbarBuilder, + ); +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/extended/widgets/text_field.dart b/local_packages/extended_text_field-16.0.2/lib/src/extended/widgets/text_field.dart new file mode 100644 index 0000000..271333f --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/extended/widgets/text_field.dart @@ -0,0 +1,649 @@ +import 'dart:async'; +import 'dart:collection'; +import 'dart:math' as math; +import 'dart:ui' as ui; + +import 'package:extended_text_field/src/extended/cupertino/spell_check_suggestions_toolbar.dart'; +import 'package:extended_text_field/src/extended/material/spell_check_suggestions_toolbar.dart'; +import 'package:extended_text_library/extended_text_library.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; + +part 'package:extended_text_field/src/extended/rendering/editable.dart'; +part 'package:extended_text_field/src/extended/widgets/editable_text.dart'; +part 'package:extended_text_field/src/extended/widgets/spell_check.dart'; +part 'package:extended_text_field/src/extended/widgets/text_selection.dart'; +part 'package:extended_text_field/src/extended/material/selectable_text.dart'; +part 'package:extended_text_field/src/official/rendering/editable.dart'; +part 'package:extended_text_field/src/official/widgets/editable_text.dart'; +part 'package:extended_text_field/src/official/widgets/text_field.dart'; +part 'package:extended_text_field/src/official/widgets/text_selection.dart'; +part 'package:extended_text_field/src/official/widgets/spell_check.dart'; +part 'package:extended_text_field/src/official/material/selectable_text.dart'; + +class ExtendedTextField extends _TextField { + const ExtendedTextField({ + super.key, + super.groupId = EditableText, + super.controller, + super.focusNode, + super.undoController, + super.decoration = const InputDecoration(), + super.keyboardType, + super.textInputAction, + super.textCapitalization = TextCapitalization.none, + super.style, + super.strutStyle, + super.textAlign = TextAlign.start, + super.textAlignVertical, + super.textDirection, + super.readOnly = false, + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + super.toolbarOptions, + super.showCursor, + super.autofocus = false, + super.statesController, + super.obscuringCharacter = '•', + super.obscureText = false, + super.autocorrect = true, + super.smartDashesType, + super.smartQuotesType, + super.enableSuggestions = true, + super.maxLines = 1, + super.minLines, + super.expands = false, + super.maxLength, + super.maxLengthEnforcement, + super.onChanged, + super.onEditingComplete, + super.onSubmitted, + super.onAppPrivateCommand, + super.inputFormatters, + super.enabled, + super.cursorWidth = 2.0, + super.cursorHeight, + super.cursorRadius, + super.cursorOpacityAnimates, + super.cursorColor, + super.cursorErrorColor, + super.selectionHeightStyle = ui.BoxHeightStyle.tight, + super.selectionWidthStyle = ui.BoxWidthStyle.tight, + super.keyboardAppearance, + super.scrollPadding = const EdgeInsets.all(20.0), + super.dragStartBehavior = DragStartBehavior.start, + super.enableInteractiveSelection, + super.selectionControls, + super.onTap, + super.onTapAlwaysCalled = false, + super.onTapOutside, + super.mouseCursor, + super.buildCounter, + super.scrollController, + super.scrollPhysics, + super.autofillHints = const [], + super.contentInsertionConfiguration, + super.clipBehavior = Clip.hardEdge, + super.restorationId, + super.scribbleEnabled = true, + super.enableIMEPersonalizedLearning = true, + // zmtzwqlp + // super.contextMenuBuilder = _defaultContextMenuBuilder, + this.extendedContextMenuBuilder = _defaultContextMenuBuilder, + super.canRequestFocus = true, + // zmtzawqlp + // super.spellCheckConfiguration, + this.extendedSpellCheckConfiguration, + this.specialTextSpanBuilder, + super.magnifierConfiguration, + }); + + /// build your ccustom text span + final SpecialTextSpanBuilder? specialTextSpanBuilder; + + /// {@template flutter.widgets.EditableText.contextMenuBuilder} + /// Builds the text selection toolbar when requested by the user. + /// + /// `primaryAnchor` is the desired anchor position for the context menu, while + /// `secondaryAnchor` is the fallback location if the menu doesn't fit. + /// + /// `buttonItems` represents the buttons that would be built by default for + /// this widget. + /// + /// {@tool dartpad} + /// This example shows how to customize the menu, in this case by keeping the + /// default buttons for the platform but modifying their appearance. + /// + /// ** See code in examples/api/lib/material/context_menu/editable_text_toolbar_builder.0.dart ** + /// {@end-tool} + /// + /// {@tool dartpad} + /// This example shows how to show a custom button only when an email address + /// is currently selected. + /// + /// ** See code in examples/api/lib/material/context_menu/editable_text_toolbar_builder.1.dart ** + /// {@end-tool} + /// + /// See also: + /// * [AdaptiveTextSelectionToolbar], which builds the default text selection + /// toolbar for the current platform, but allows customization of the + /// buttons. + /// * [AdaptiveTextSelectionToolbar.getAdaptiveButtons], which builds the + /// button Widgets for the current platform given + /// [ContextMenuButtonItem]s. + /// * [BrowserContextMenu], which allows the browser's context menu on web + /// to be disabled and Flutter-rendered context menus to appear. + /// {@endtemplate} + /// + /// If not provided, no context menu will be shown. + final ExtendedEditableTextContextMenuBuilder? extendedContextMenuBuilder; + + /// {@template flutter.widgets.EditableText.spellCheckConfiguration} + /// Configuration that details how spell check should be performed. + /// + /// Specifies the [SpellCheckService] used to spell check text input and the + /// [TextStyle] used to style text with misspelled words. + /// + /// If the [SpellCheckService] is left null, spell check is disabled by + /// default unless the [DefaultSpellCheckService] is supported, in which case + /// it is used. It is currently supported only on Android and iOS. + /// + /// If this configuration is left null, then spell check is disabled by default. + /// {@endtemplate} + final ExtendedSpellCheckConfiguration? extendedSpellCheckConfiguration; + + /// zmtzawqlp + /// [AdaptiveTextSelectionToolbar.editableText] + static Widget _defaultContextMenuBuilder( + BuildContext context, ExtendedEditableTextState editableTextState) { + return AdaptiveTextSelectionToolbar.buttonItems( + buttonItems: editableTextState.contextMenuButtonItems, + anchors: editableTextState.contextMenuAnchors, + ); + // return AdaptiveTextSelectionToolbar.editableText( + // editableTextState: editableTextState, + // ); + } + + /// Returns a new [SpellCheckConfiguration] where the given configuration has + /// had any missing values replaced with their defaults for the Android + /// platform. + static ExtendedSpellCheckConfiguration inferAndroidSpellCheckConfiguration( + ExtendedSpellCheckConfiguration? configuration, + ) { + if (configuration == null || + configuration == const ExtendedSpellCheckConfiguration.disabled()) { + return const ExtendedSpellCheckConfiguration.disabled(); + } + return configuration.copyWith( + misspelledTextStyle: configuration.misspelledTextStyle ?? + TextField.materialMisspelledTextStyle, + extendedSpellCheckSuggestionsToolbarBuilder: + configuration.extendedSpellCheckSuggestionsToolbarBuilder ?? + ExtendedTextField.defaultSpellCheckSuggestionsToolbarBuilder, + // spellCheckSuggestionsToolbarBuilder: + // configuration.spellCheckSuggestionsToolbarBuilder ?? + // TextField.defaultSpellCheckSuggestionsToolbarBuilder, + ) as ExtendedSpellCheckConfiguration; + } + + /// Default builder for [TextField]'s spell check suggestions toolbar. + /// + /// On Apple platforms, builds an iOS-style toolbar. Everywhere else, builds + /// an Android-style toolbar. + /// + /// See also: + /// * [spellCheckConfiguration], where this is typically specified for + /// [TextField]. + /// * [SpellCheckConfiguration.spellCheckSuggestionsToolbarBuilder], the + /// parameter for which this is the default value for [TextField]. + /// * [CupertinoTextField.defaultSpellCheckSuggestionsToolbarBuilder], which + /// is like this but specifies the default for [CupertinoTextField]. + /// [TextField.defaultSpellCheckSuggestionsToolbarBuilder] + @visibleForTesting + static Widget defaultSpellCheckSuggestionsToolbarBuilder( + BuildContext context, + ExtendedEditableTextState editableTextState, + ) { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + return ExtendedCupertinoSpellCheckSuggestionsToolbar.editableText( + editableTextState: editableTextState, + ); + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + return ExtendedSpellCheckSuggestionsToolbar.editableText( + editableTextState: editableTextState, + ); + } + } + + /// Returns a new [SpellCheckConfiguration] where the given configuration has + /// had any missing values replaced with their defaults for the iOS platform. + static ExtendedSpellCheckConfiguration inferIOSSpellCheckConfiguration( + ExtendedSpellCheckConfiguration? configuration, + ) { + if (configuration == null || + configuration == const ExtendedSpellCheckConfiguration.disabled()) { + return const ExtendedSpellCheckConfiguration.disabled(); + } + + return configuration.copyWith( + misspelledTextStyle: configuration.misspelledTextStyle ?? + CupertinoTextField.cupertinoMisspelledTextStyle, + misspelledSelectionColor: configuration.misspelledSelectionColor ?? + // ignore: invalid_use_of_visible_for_testing_member + CupertinoTextField.kMisspelledSelectionColor, + extendedSpellCheckSuggestionsToolbarBuilder: + configuration.extendedSpellCheckSuggestionsToolbarBuilder ?? + defaultIosSpellCheckSuggestionsToolbarBuilder, + // spellCheckSuggestionsToolbarBuilder: + // configuration.spellCheckSuggestionsToolbarBuilder + // ?? CupertinoTextField.defaultSpellCheckSuggestionsToolbarBuilder, + ) as ExtendedSpellCheckConfiguration; + } + + /// Default builder for the spell check suggestions toolbar in the Cupertino + /// style. + /// + /// See also: + /// * [spellCheckConfiguration], where this is typically specified for + /// [CupertinoTextField]. + /// * [SpellCheckConfiguration.spellCheckSuggestionsToolbarBuilder], the + /// parameter for which this is the default value for [CupertinoTextField]. + /// * [TextField.defaultSpellCheckSuggestionsToolbarBuilder], which is like + /// this but specifies the default for [CupertinoTextField]. + /// [CupertinoTextField.defaultSpellCheckSuggestionsToolbarBuilder] + @visibleForTesting + static Widget defaultIosSpellCheckSuggestionsToolbarBuilder( + BuildContext context, + ExtendedEditableTextState editableTextState, + ) { + return ExtendedCupertinoSpellCheckSuggestionsToolbar.editableText( + editableTextState: editableTextState, + ); + } + + @override + State<_TextField> createState() { + return ExtendedTextFieldState(); + } +} + +class ExtendedTextFieldState extends _TextFieldState { + ExtendedTextField get extenedTextField => widget as ExtendedTextField; + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMaterial(context)); + assert(debugCheckHasMaterialLocalizations(context)); + assert(debugCheckHasDirectionality(context)); + assert( + !(widget.style != null && + !widget.style!.inherit && + (widget.style!.fontSize == null || + widget.style!.textBaseline == null)), + 'inherit false style must supply fontSize and textBaseline', + ); + + final ThemeData theme = Theme.of(context); + final DefaultSelectionStyle selectionStyle = + DefaultSelectionStyle.of(context); + final TextStyle? providedStyle = + MaterialStateProperty.resolveAs(widget.style, _statesController.value); + final TextStyle style = _getInputStyleForState(theme.useMaterial3 + ? _m3InputStyle(context) + : theme.textTheme.titleMedium!) + .merge(providedStyle); + final Brightness keyboardAppearance = + widget.keyboardAppearance ?? theme.brightness; + final TextEditingController controller = _effectiveController; + final FocusNode focusNode = _effectiveFocusNode; + final List formatters = [ + ...?widget.inputFormatters, + if (widget.maxLength != null) + LengthLimitingTextInputFormatter( + widget.maxLength, + maxLengthEnforcement: _effectiveMaxLengthEnforcement, + ), + ]; + + // Set configuration as disabled if not otherwise specified. If specified, + // ensure that configuration uses the correct style for misspelled words for + // the current platform, unless a custom style is specified. + // zmtzawqlp + final ExtendedSpellCheckConfiguration spellCheckConfiguration; + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + // zmtzawqlp + spellCheckConfiguration = + ExtendedTextField.inferIOSSpellCheckConfiguration( + extenedTextField.extendedSpellCheckConfiguration, + ); + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + // zmtzawqlp + spellCheckConfiguration = + ExtendedTextField.inferAndroidSpellCheckConfiguration( + extenedTextField.extendedSpellCheckConfiguration, + ); + } + + TextSelectionControls? textSelectionControls = widget.selectionControls; + final bool paintCursorAboveText; + bool? cursorOpacityAnimates = widget.cursorOpacityAnimates; + Offset? cursorOffset; + final Color cursorColor; + final Color selectionColor; + Color? autocorrectionTextRectColor; + Radius? cursorRadius = widget.cursorRadius; + VoidCallback? handleDidGainAccessibilityFocus; + VoidCallback? handleDidLoseAccessibilityFocus; + + switch (theme.platform) { + case TargetPlatform.iOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + forcePressEnabled = true; + textSelectionControls ??= cupertinoTextSelectionHandleControls; + paintCursorAboveText = true; + cursorOpacityAnimates ??= true; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + cupertinoTheme.primaryColor; + selectionColor = selectionStyle.selectionColor ?? + cupertinoTheme.primaryColor.withOpacity(0.40); + cursorRadius ??= const Radius.circular(2.0); + cursorOffset = Offset( + iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0); + autocorrectionTextRectColor = selectionColor; + + case TargetPlatform.macOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + forcePressEnabled = false; + textSelectionControls ??= cupertinoDesktopTextSelectionHandleControls; + paintCursorAboveText = true; + cursorOpacityAnimates ??= false; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + cupertinoTheme.primaryColor; + selectionColor = selectionStyle.selectionColor ?? + cupertinoTheme.primaryColor.withOpacity(0.40); + cursorRadius ??= const Radius.circular(2.0); + cursorOffset = Offset( + iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0); + handleDidGainAccessibilityFocus = () { + // Automatically activate the TextField when it receives accessibility focus. + if (!_effectiveFocusNode.hasFocus && + _effectiveFocusNode.canRequestFocus) { + _effectiveFocusNode.requestFocus(); + } + }; + handleDidLoseAccessibilityFocus = () { + _effectiveFocusNode.unfocus(); + }; + + case TargetPlatform.android: + case TargetPlatform.fuchsia: + forcePressEnabled = false; + textSelectionControls ??= materialTextSelectionHandleControls; + paintCursorAboveText = false; + cursorOpacityAnimates ??= false; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + theme.colorScheme.primary; + selectionColor = selectionStyle.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + + case TargetPlatform.linux: + forcePressEnabled = false; + textSelectionControls ??= desktopTextSelectionHandleControls; + paintCursorAboveText = false; + cursorOpacityAnimates ??= false; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + theme.colorScheme.primary; + selectionColor = selectionStyle.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + handleDidGainAccessibilityFocus = () { + // Automatically activate the TextField when it receives accessibility focus. + if (!_effectiveFocusNode.hasFocus && + _effectiveFocusNode.canRequestFocus) { + _effectiveFocusNode.requestFocus(); + } + }; + handleDidLoseAccessibilityFocus = () { + _effectiveFocusNode.unfocus(); + }; + + case TargetPlatform.windows: + forcePressEnabled = false; + textSelectionControls ??= desktopTextSelectionHandleControls; + paintCursorAboveText = false; + cursorOpacityAnimates ??= false; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + theme.colorScheme.primary; + selectionColor = selectionStyle.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + handleDidGainAccessibilityFocus = () { + // Automatically activate the TextField when it receives accessibility focus. + if (!_effectiveFocusNode.hasFocus && + _effectiveFocusNode.canRequestFocus) { + _effectiveFocusNode.requestFocus(); + } + }; + handleDidLoseAccessibilityFocus = () { + _effectiveFocusNode.unfocus(); + }; + } + + Widget child = RepaintBoundary( + child: UnmanagedRestorationScope( + bucket: bucket, + child: ExtendedEditableText( + key: editableTextKey, + readOnly: widget.readOnly || !_isEnabled, + toolbarOptions: widget.toolbarOptions, + showCursor: widget.showCursor, + showSelectionHandles: _showSelectionHandles, + controller: controller, + focusNode: focusNode, + undoController: widget.undoController, + keyboardType: widget.keyboardType, + textInputAction: widget.textInputAction, + textCapitalization: widget.textCapitalization, + style: style, + strutStyle: widget.strutStyle, + textAlign: widget.textAlign, + textDirection: widget.textDirection, + autofocus: widget.autofocus, + obscuringCharacter: widget.obscuringCharacter, + obscureText: widget.obscureText, + autocorrect: widget.autocorrect, + smartDashesType: widget.smartDashesType, + smartQuotesType: widget.smartQuotesType, + enableSuggestions: widget.enableSuggestions, + maxLines: widget.maxLines, + minLines: widget.minLines, + expands: widget.expands, + // Only show the selection highlight when the text field is focused. + selectionColor: focusNode.hasFocus ? selectionColor : null, + selectionControls: + widget.selectionEnabled ? textSelectionControls : null, + onChanged: widget.onChanged, + onSelectionChanged: _handleSelectionChanged, + onEditingComplete: widget.onEditingComplete, + onSubmitted: widget.onSubmitted, + onAppPrivateCommand: widget.onAppPrivateCommand, + groupId: widget.groupId, + onSelectionHandleTapped: _handleSelectionHandleTapped, + onTapOutside: widget.onTapOutside, + inputFormatters: formatters, + rendererIgnoresPointer: true, + mouseCursor: MouseCursor.defer, // TextField will handle the cursor + cursorWidth: widget.cursorWidth, + cursorHeight: widget.cursorHeight, + cursorRadius: cursorRadius, + cursorColor: cursorColor, + selectionHeightStyle: widget.selectionHeightStyle, + selectionWidthStyle: widget.selectionWidthStyle, + cursorOpacityAnimates: cursorOpacityAnimates, + cursorOffset: cursorOffset, + paintCursorAboveText: paintCursorAboveText, + backgroundCursorColor: CupertinoColors.inactiveGray, + scrollPadding: widget.scrollPadding, + keyboardAppearance: keyboardAppearance, + enableInteractiveSelection: widget.enableInteractiveSelection, + dragStartBehavior: widget.dragStartBehavior, + scrollController: widget.scrollController, + scrollPhysics: widget.scrollPhysics, + autofillClient: this, + autocorrectionTextRectColor: autocorrectionTextRectColor, + clipBehavior: widget.clipBehavior, + restorationId: 'editable', + scribbleEnabled: widget.scribbleEnabled, + enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, + contentInsertionConfiguration: widget.contentInsertionConfiguration, + // contextMenuBuilder: widget.contextMenuBuilder, + // spellCheckConfiguration: spellCheckConfiguration, + extendedContextMenuBuilder: + extenedTextField.extendedContextMenuBuilder, + extendedSpellCheckConfiguration: spellCheckConfiguration, + magnifierConfiguration: widget.magnifierConfiguration ?? + TextMagnifier.adaptiveMagnifierConfiguration, + // zmtzawqlp + specialTextSpanBuilder: extenedTextField.specialTextSpanBuilder, + ), + ), + ); + + if (widget.decoration != null) { + child = AnimatedBuilder( + animation: Listenable.merge([focusNode, controller]), + builder: (BuildContext context, Widget? child) { + return InputDecorator( + decoration: _getEffectiveDecoration(), + baseStyle: widget.style, + textAlign: widget.textAlign, + textAlignVertical: widget.textAlignVertical, + isHovering: _isHovering, + isFocused: focusNode.hasFocus, + isEmpty: controller.value.text.isEmpty, + expands: widget.expands, + child: child, + ); + }, + child: child, + ); + } + final MouseCursor effectiveMouseCursor = + MaterialStateProperty.resolveAs( + widget.mouseCursor ?? MaterialStateMouseCursor.textable, + _statesController.value, + ); + + final int? semanticsMaxValueLength; + if (_effectiveMaxLengthEnforcement != MaxLengthEnforcement.none && + widget.maxLength != null && + widget.maxLength! > 0) { + semanticsMaxValueLength = widget.maxLength; + } else { + semanticsMaxValueLength = null; + } + + return MouseRegion( + cursor: effectiveMouseCursor, + onEnter: (PointerEnterEvent event) => _handleHover(true), + onExit: (PointerExitEvent event) => _handleHover(false), + child: TextFieldTapRegion( + child: IgnorePointer( + ignoring: widget.ignorePointers ?? !_isEnabled, + child: AnimatedBuilder( + animation: controller, // changes the _currentLength + builder: (BuildContext context, Widget? child) { + return Semantics( + enabled: _isEnabled, + maxValueLength: semanticsMaxValueLength, + currentValueLength: _currentLength, + onTap: widget.readOnly + ? null + : () { + if (!_effectiveController.selection.isValid) { + _effectiveController.selection = + TextSelection.collapsed( + offset: _effectiveController.text.length); + } + _requestKeyboard(); + }, + onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus, + onDidLoseAccessibilityFocus: handleDidLoseAccessibilityFocus, + onFocus: _isEnabled + ? () { + assert( + _effectiveFocusNode.canRequestFocus, + 'Received SemanticsAction.focus from the engine. However, the FocusNode ' + 'of this text field cannot gain focus. This likely indicates a bug. ' + 'If this text field cannot be focused (e.g. because it is not ' + 'enabled), then its corresponding semantics node must be configured ' + 'such that the assistive technology cannot request focus on it.'); + + if (_effectiveFocusNode.canRequestFocus && + !_effectiveFocusNode.hasFocus) { + _effectiveFocusNode.requestFocus(); + } else if (!widget.readOnly) { + // If the platform requested focus, that means that previously the + // platform believed that the text field did not have focus (even + // though Flutter's widget system believed otherwise). This likely + // means that the on-screen keyboard is hidden, or more generally, + // there is no current editing session in this field. To correct + // that, keyboard must be requested. + // + // A concrete scenario where this can happen is when the user + // dismisses the keyboard on the web. The editing session is + // closed by the engine, but the text field widget stays focused + // in the framework. + _requestKeyboard(); + } + } + : null, + child: child, + ); + }, + child: _selectionGestureDetectorBuilder.buildGestureDetector( + behavior: HitTestBehavior.translucent, + child: child, + ), + ), + ), + ), + ); + } + + void bringIntoView(TextPosition position, {double offset = 0}) { + (_editableText as ExtendedEditableTextState?) + ?.bringIntoView(position, offset: offset); + } +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/extended/widgets/text_selection.dart b/local_packages/extended_text_field-16.0.2/lib/src/extended/widgets/text_selection.dart new file mode 100644 index 0000000..f2463b4 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/extended/widgets/text_selection.dart @@ -0,0 +1,222 @@ +part of 'package:extended_text_field/src/extended/widgets/text_field.dart'; + +/// [TextSelectionOverlay ] +class ExtendedTextSelectionOverlay extends _TextSelectionOverlay { + ExtendedTextSelectionOverlay({ + required super.value, + required super.context, + super.debugRequiredFor, + required super.toolbarLayerLink, + required super.startHandleLayerLink, + required super.endHandleLayerLink, + required super.renderObject, + super.selectionControls, + super.handlesVisible = false, + required super.selectionDelegate, + super.dragStartBehavior = DragStartBehavior.start, + super.onSelectionHandleTapped, + super.clipboardStatus, + super.contextMenuBuilder, + required super.magnifierConfiguration, + }); + + @override + void _handleSelectionStartHandleDragUpdate(DragUpdateDetails details) { + if (!renderObject.attached) { + return; + } + + // This is NOT the same as details.localPosition. That is relative to the + // selection handle, whereas this is relative to the RenderEditable. + final Offset localPosition = + renderObject.globalToLocal(details.globalPosition); + final double nextStartHandleDragPositionLocal = _getHandleDy( + localPosition.dy, + renderObject.globalToLocal(Offset(0.0, _startHandleDragPosition)).dy, + ); + _startHandleDragPosition = renderObject + .localToGlobal( + Offset(0.0, nextStartHandleDragPositionLocal), + ) + .dy; + final Offset handleTargetGlobal = Offset( + details.globalPosition.dx, + _startHandleDragPosition + _startHandleDragTarget, + ); + TextPosition position = + renderObject.getPositionForPoint(handleTargetGlobal); + + /// zmtzawqlp + final bool hasSpecialInlineSpanBase = + (renderObject as ExtendedRenderEditable).hasSpecialInlineSpanBase; + if (hasSpecialInlineSpanBase) { + position = + ExtendedTextLibraryUtils.convertTextPainterPostionToTextInputPostion( + renderObject.text!, position)!; + } + if (_selection.isCollapsed) { + _selectionOverlay.updateMagnifier(_buildMagnifier( + currentTextPosition: position, + globalGesturePosition: details.globalPosition, + renderEditable: renderObject, + hasSpecialInlineSpanBase: hasSpecialInlineSpanBase, + )); + + final TextSelection currentSelection = + TextSelection.fromPosition(position); + _handleSelectionHandleChanged(currentSelection); + return; + } + + final TextSelection newSelection; + switch (defaultTargetPlatform) { + // On Apple platforms, dragging the base handle makes it the extent. + case TargetPlatform.iOS: + case TargetPlatform.macOS: + newSelection = TextSelection( + extentOffset: position.offset, + baseOffset: _selection.end, + ); + if (newSelection.extentOffset >= _selection.end) { + return; // Don't allow order swapping. + } + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + newSelection = TextSelection( + baseOffset: position.offset, + extentOffset: _selection.extentOffset, + ); + if (newSelection.baseOffset >= newSelection.extentOffset) { + return; // Don't allow order swapping. + } + } + + _selectionOverlay.updateMagnifier(_buildMagnifier( + currentTextPosition: newSelection.extent.offset < newSelection.base.offset + ? newSelection.extent + : newSelection.base, + globalGesturePosition: details.globalPosition, + renderEditable: renderObject, + hasSpecialInlineSpanBase: hasSpecialInlineSpanBase, + )); + + _handleSelectionHandleChanged(newSelection); + } + + @override + void _handleSelectionEndHandleDragUpdate(DragUpdateDetails details) { + if (!renderObject.attached) { + return; + } + + // This is NOT the same as details.localPosition. That is relative to the + // selection handle, whereas this is relative to the RenderEditable. + final Offset localPosition = + renderObject.globalToLocal(details.globalPosition); + + final double nextEndHandleDragPositionLocal = _getHandleDy( + localPosition.dy, + renderObject.globalToLocal(Offset(0.0, _endHandleDragPosition)).dy, + ); + _endHandleDragPosition = renderObject + .localToGlobal( + Offset(0.0, nextEndHandleDragPositionLocal), + ) + .dy; + + final Offset handleTargetGlobal = Offset( + details.globalPosition.dx, + _endHandleDragPosition + _endHandleDragTarget, + ); + + TextPosition position = + renderObject.getPositionForPoint(handleTargetGlobal); + // zmtzawqlp + final bool hasSpecialInlineSpanBase = + (renderObject as ExtendedRenderEditable).hasSpecialInlineSpanBase; + if (hasSpecialInlineSpanBase) { + position = + ExtendedTextLibraryUtils.convertTextPainterPostionToTextInputPostion( + renderObject.text!, position)!; + } + if (_selection.isCollapsed) { + _selectionOverlay.updateMagnifier(_buildMagnifier( + currentTextPosition: position, + globalGesturePosition: details.globalPosition, + renderEditable: renderObject, + hasSpecialInlineSpanBase: hasSpecialInlineSpanBase, + )); + + final TextSelection currentSelection = + TextSelection.fromPosition(position); + _handleSelectionHandleChanged(currentSelection); + return; + } + + final TextSelection newSelection; + switch (defaultTargetPlatform) { + // On Apple platforms, dragging the base handle makes it the extent. + case TargetPlatform.iOS: + case TargetPlatform.macOS: + newSelection = TextSelection( + extentOffset: position.offset, + baseOffset: _selection.start, + ); + if (position.offset <= _selection.start) { + return; // Don't allow order swapping. + } + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + newSelection = TextSelection( + baseOffset: _selection.baseOffset, + extentOffset: position.offset, + ); + if (newSelection.baseOffset >= newSelection.extentOffset) { + return; // Don't allow order swapping. + } + } + + _handleSelectionHandleChanged(newSelection); + + _selectionOverlay.updateMagnifier(_buildMagnifier( + currentTextPosition: newSelection.extent, + globalGesturePosition: details.globalPosition, + renderEditable: renderObject, + hasSpecialInlineSpanBase: hasSpecialInlineSpanBase, + )); + } + + @override + MagnifierInfo _buildMagnifier({ + required _RenderEditable renderEditable, + required ui.Offset globalGesturePosition, + required ui.TextPosition currentTextPosition, + bool hasSpecialInlineSpanBase = false, + }) { + // zmtzawqlp + if (hasSpecialInlineSpanBase) { + currentTextPosition = + ExtendedTextLibraryUtils.convertTextInputPostionToTextPainterPostion( + renderObject.text!, currentTextPosition); + } + return super._buildMagnifier( + renderEditable: renderEditable, + globalGesturePosition: globalGesturePosition, + currentTextPosition: currentTextPosition); + } + + // @override + // void _handleSelectionHandleChanged(TextSelection newSelection) { + // // zmtzawqlp + // if ((renderObject as ExtendedRenderEditable).hasSpecialInlineSpanBase) { + // newSelection = ExtendedTextLibraryUtils + // .convertTextPainterSelectionToTextInputSelection( + // renderObject.text!, newSelection); + // } + // super._handleSelectionHandleChanged(newSelection); + // } +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/keyboard/binding.dart b/local_packages/extended_text_field-16.0.2/lib/src/keyboard/binding.dart new file mode 100644 index 0000000..89435de --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/keyboard/binding.dart @@ -0,0 +1,86 @@ +import 'package:extended_text_field/src/keyboard/focus_node.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +/// void main() { +/// TextInputBinding(); +/// runApp(const MyApp()); +/// } +class TextInputBinding extends WidgetsFlutterBinding + with TextInputBindingMixin {} + +/// class YourBinding extends WidgetsFlutterBinding with TextInputBindingMixin,YourBindingMixin { +/// @override +/// // ignore: unnecessary_overrides +/// bool ignoreTextInputShow() { +/// // you can override it base on your case +/// // if NoKeyboardFocusNode is not enough +/// return super.ignoreTextInputShow(); +/// } +/// } +/// +/// void main() { +/// YourBinding(); +/// runApp(const MyApp()); +/// } +mixin TextInputBindingMixin on WidgetsFlutterBinding { + @override + BinaryMessenger createBinaryMessenger() { + return TextInputBinaryMessenger(super.createBinaryMessenger(), this); + } + + bool ignoreSendMessage(MethodCall methodCall) => false; + + bool ignoreTextInputShow() { + final FocusNode? focus = FocusManager.instance.primaryFocus; + if (focus != null && + focus is TextInputFocusNode && + focus.ignoreSystemKeyboardShow) { + return true; + } + return false; + } +} + +class TextInputBinaryMessenger extends BinaryMessenger { + TextInputBinaryMessenger(this.origin, this.textInputBindingMixin); + final BinaryMessenger origin; + final TextInputBindingMixin textInputBindingMixin; + @override + Future handlePlatformMessage(String channel, ByteData? data, + PlatformMessageResponseCallback? callback) async { + ServicesBinding.instance.channelBuffers.push( + channel, + data, + (ByteData? data) { + callback?.call(data); + }, + ); + } + + @override + Future? send(String channel, ByteData? message) async { + if (channel == SystemChannels.textInput.name) { + final MethodCall methodCall = + SystemChannels.textInput.codec.decodeMethodCall(message); + bool ignore = false; + switch (methodCall.method) { + case 'TextInput.show': + ignore = textInputBindingMixin.ignoreTextInputShow(); + break; + default: + ignore = textInputBindingMixin.ignoreSendMessage(methodCall); + } + + if (ignore) { + return null; + } + } + return origin.send(channel, message); + } + + @override + void setMessageHandler(String channel, MessageHandler? handler) { + origin.setMessageHandler(channel, handler); + } +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/keyboard/focus_node.dart b/local_packages/extended_text_field-16.0.2/lib/src/keyboard/focus_node.dart new file mode 100644 index 0000000..d4ec7da --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/keyboard/focus_node.dart @@ -0,0 +1,9 @@ +import 'package:flutter/widgets.dart'; + +/// The FocusNode to be used in [TextInputBindingMixin] +/// +class TextInputFocusNode extends FocusNode { + /// no system keyboard show + /// if it's true, it stop Flutter Framework send `TextInput.show` message to Flutter Engine + bool ignoreSystemKeyboardShow = true; +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/official/material/selectable_text.dart b/local_packages/extended_text_field-16.0.2/lib/src/official/material/selectable_text.dart new file mode 100644 index 0000000..58d6a4f --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/official/material/selectable_text.dart @@ -0,0 +1,835 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: overridden_fields + +part of 'package:extended_text_field/src/extended/widgets/text_field.dart'; + +// Examples can assume: +// late BuildContext context; +// late FocusNode myFocusNode; + +/// An eyeballed value that moves the cursor slightly left of where it is +/// rendered for text on Android so its positioning more accurately matches the +/// native iOS text cursor positioning. +/// +/// This value is in device pixels, not logical pixels as is typically used +/// throughout the codebase. +const int iOSHorizontalOffset = -2; + +class _TextSpanEditingController extends TextEditingController { + _TextSpanEditingController({required TextSpan textSpan}) + : _textSpan = textSpan, + super(text: textSpan.toPlainText(includeSemanticsLabels: false)); + + final TextSpan _textSpan; + + @override + TextSpan buildTextSpan( + {required BuildContext context, + TextStyle? style, + required bool withComposing}) { + // This does not care about composing. + return TextSpan( + style: style, + children: [_textSpan], + ); + } + + @override + set text(String? newText) { + // This should never be reached. + throw UnimplementedError(); + } +} + +class _SelectableTextSelectionGestureDetectorBuilder + // zmtzawqlp + extends _TextSelectionGestureDetectorBuilder { + _SelectableTextSelectionGestureDetectorBuilder({ + required _SelectableTextState state, + }) : _state = state, + super(delegate: state); + + final _SelectableTextState _state; + + /// The viewport offset pixels of any [Scrollable] containing the + /// [RenderEditable] at the last drag start. + @override + double _dragStartScrollOffset = 0.0; + + /// The viewport offset pixels of the [RenderEditable] at the last drag start. + @override + double _dragStartViewportOffset = 0.0; + + @override + double get _scrollPosition { + final ScrollableState? scrollableState = + delegate.editableTextKey.currentContext == null + ? null + : Scrollable.maybeOf(delegate.editableTextKey.currentContext!); + return scrollableState == null ? 0.0 : scrollableState.position.pixels; + } + + @override + AxisDirection? get _scrollDirection { + final ScrollableState? scrollableState = + delegate.editableTextKey.currentContext == null + ? null + : Scrollable.maybeOf(delegate.editableTextKey.currentContext!); + return scrollableState?.axisDirection; + } + + @override + void onForcePressStart(ForcePressDetails details) { + super.onForcePressStart(details); + if (delegate.selectionEnabled && shouldShowSelectionToolbar) { + editableText.showToolbar(); + } + } + + @override + void onForcePressEnd(ForcePressDetails details) { + // Not required. + } + + @override + void onSingleLongTapStart(LongPressStartDetails details) { + if (!delegate.selectionEnabled) { + return; + } + renderEditable.selectWord(cause: SelectionChangedCause.longPress); + Feedback.forLongPress(_state.context); + _dragStartViewportOffset = renderEditable.offset.pixels; + _dragStartScrollOffset = _scrollPosition; + } + + @override + void onSingleLongTapMoveUpdate(LongPressMoveUpdateDetails details) { + if (!delegate.selectionEnabled) { + return; + } + // Adjust the drag start offset for possible viewport offset changes. + final Offset editableOffset = renderEditable.maxLines == 1 + ? Offset(renderEditable.offset.pixels - _dragStartViewportOffset, 0.0) + : Offset(0.0, renderEditable.offset.pixels - _dragStartViewportOffset); + final Offset scrollableOffset = + switch (axisDirectionToAxis(_scrollDirection ?? AxisDirection.left)) { + Axis.horizontal => Offset(_scrollPosition - _dragStartScrollOffset, 0), + Axis.vertical => Offset(0, _scrollPosition - _dragStartScrollOffset), + }; + renderEditable.selectWordsInRange( + from: details.globalPosition - + details.offsetFromOrigin - + editableOffset - + scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.longPress, + ); + } + + @override + void onSingleTapUp(TapDragUpDetails details) { + editableText.hideToolbar(); + if (delegate.selectionEnabled) { + switch (Theme.of(_state.context).platform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + renderEditable.selectWordEdge(cause: SelectionChangedCause.tap); + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + renderEditable.selectPosition(cause: SelectionChangedCause.tap); + } + } + _state.widget.onTap?.call(); + } +} + +/// A run of selectable text with a single style. +/// +/// Consider using [SelectionArea] or [SelectableRegion] instead, which enable +/// selection on a widget subtree, including but not limited to [Text] widgets. +/// +/// The [SelectableText] widget displays a string of text with a single style. +/// The string might break across multiple lines or might all be displayed on +/// the same line depending on the layout constraints. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=ZSU3ZXOs6hc} +/// +/// The [style] argument is optional. When omitted, the text will use the style +/// from the closest enclosing [DefaultTextStyle]. If the given style's +/// [TextStyle.inherit] property is true (the default), the given style will +/// be merged with the closest enclosing [DefaultTextStyle]. This merging +/// behavior is useful, for example, to make the text bold while using the +/// default font family and size. +/// +/// {@macro flutter.material.textfield.wantKeepAlive} +/// +/// {@tool snippet} +/// +/// ```dart +/// const SelectableText( +/// 'Hello! How are you?', +/// textAlign: TextAlign.center, +/// style: TextStyle(fontWeight: FontWeight.bold), +/// ) +/// ``` +/// {@end-tool} +/// +/// Using the [SelectableText.rich] constructor, the [SelectableText] widget can +/// display a paragraph with differently styled [TextSpan]s. The sample +/// that follows displays "Hello beautiful world" with different styles +/// for each word. +/// +/// {@tool snippet} +/// +/// ```dart +/// const SelectableText.rich( +/// TextSpan( +/// text: 'Hello', // default text style +/// children: [ +/// TextSpan(text: ' beautiful ', style: TextStyle(fontStyle: FontStyle.italic)), +/// TextSpan(text: 'world', style: TextStyle(fontWeight: FontWeight.bold)), +/// ], +/// ), +/// ) +/// ``` +/// {@end-tool} +/// +/// ## Interactivity +/// +/// To make [SelectableText] react to touch events, use callback [onTap] to achieve +/// the desired behavior. +/// +/// ## Scrolling Considerations +/// +/// If this [SelectableText] is not a descendant of [Scaffold] and is being used +/// within a [Scrollable] or nested [Scrollable]s, consider placing a +/// [ScrollNotificationObserver] above the root [Scrollable] that contains this +/// [SelectableText] to ensure proper scroll coordination for [SelectableText] +/// and its components like [TextSelectionOverlay]. +/// +/// See also: +/// +/// * [Text], which is the non selectable version of this widget. +/// * [TextField], which is the editable version of this widget. +/// * [SelectionArea], which enables the selection of multiple [Text] widgets +/// and of other widgets. +class _SelectableText extends StatefulWidget { + /// Creates a selectable text widget. + /// + /// If the [style] argument is null, the text will use the style from the + /// closest enclosing [DefaultTextStyle]. + /// + + /// If the [showCursor], [autofocus], [dragStartBehavior], + /// [selectionHeightStyle], [selectionWidthStyle] and [data] arguments are + /// specified, the [maxLines] argument must be greater than zero. + const _SelectableText( + String this.data, { + super.key, + this.focusNode, + this.style, + this.strutStyle, + this.textAlign, + this.textDirection, + this.textScaler, + this.showCursor = false, + this.autofocus = false, + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + this.toolbarOptions, + this.minLines, + this.maxLines, + this.cursorWidth = 2.0, + this.cursorHeight, + this.cursorRadius, + this.cursorColor, + this.selectionHeightStyle = ui.BoxHeightStyle.tight, + this.selectionWidthStyle = ui.BoxWidthStyle.tight, + this.dragStartBehavior = DragStartBehavior.start, + this.enableInteractiveSelection = true, + this.selectionControls, + this.onTap, + this.scrollPhysics, + this.semanticsLabel, + this.textHeightBehavior, + this.textWidthBasis, + this.onSelectionChanged, + this.contextMenuBuilder = _defaultContextMenuBuilder, + this.magnifierConfiguration, + }) : assert(maxLines == null || maxLines > 0), + assert(minLines == null || minLines > 0), + assert( + (maxLines == null) || (minLines == null) || (maxLines >= minLines), + "minLines can't be greater than maxLines", + ), + textSpan = null; + + /// Creates a selectable text widget with a [TextSpan]. + /// + /// The [TextSpan.children] attribute of the [textSpan] parameter must only + /// contain [TextSpan]s. Other types of [InlineSpan] are not allowed. + const _SelectableText.rich( + TextSpan this.textSpan, { + super.key, + this.focusNode, + this.style, + this.strutStyle, + this.textAlign, + this.textDirection, + this.textScaler, + this.showCursor = false, + this.autofocus = false, + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + this.toolbarOptions, + this.minLines, + this.maxLines, + this.cursorWidth = 2.0, + this.cursorHeight, + this.cursorRadius, + this.cursorColor, + this.selectionHeightStyle = ui.BoxHeightStyle.tight, + this.selectionWidthStyle = ui.BoxWidthStyle.tight, + this.dragStartBehavior = DragStartBehavior.start, + this.enableInteractiveSelection = true, + this.selectionControls, + this.onTap, + this.scrollPhysics, + this.semanticsLabel, + this.textHeightBehavior, + this.textWidthBasis, + this.onSelectionChanged, + this.contextMenuBuilder = _defaultContextMenuBuilder, + this.magnifierConfiguration, + }) : assert(maxLines == null || maxLines > 0), + assert(minLines == null || minLines > 0), + assert( + (maxLines == null) || (minLines == null) || (maxLines >= minLines), + "minLines can't be greater than maxLines", + ), + data = null; + + /// The text to display. + /// + /// This will be null if a [textSpan] is provided instead. + final String? data; + + /// The text to display as a [TextSpan]. + /// + /// This will be null if [data] is provided instead. + final TextSpan? textSpan; + + /// Defines the focus for this widget. + /// + /// Text is only selectable when widget is focused. + /// + /// The [focusNode] is a long-lived object that's typically managed by a + /// [StatefulWidget] parent. See [FocusNode] for more information. + /// + /// To give the focus to this widget, provide a [focusNode] and then + /// use the current [FocusScope] to request the focus: + /// + /// ```dart + /// FocusScope.of(context).requestFocus(myFocusNode); + /// ``` + /// + /// This happens automatically when the widget is tapped. + /// + /// To be notified when the widget gains or loses the focus, add a listener + /// to the [focusNode]: + /// + /// ```dart + /// myFocusNode.addListener(() { print(myFocusNode.hasFocus); }); + /// ``` + /// + /// If null, this widget will create its own [FocusNode] with + /// [FocusNode.skipTraversal] parameter set to `true`, which causes the widget + /// to be skipped over during focus traversal. + final FocusNode? focusNode; + + /// The style to use for the text. + /// + /// If null, defaults [DefaultTextStyle] of context. + final TextStyle? style; + + /// {@macro flutter.widgets.editableText.strutStyle} + final StrutStyle? strutStyle; + + /// {@macro flutter.widgets.editableText.textAlign} + final TextAlign? textAlign; + + /// {@macro flutter.widgets.editableText.textDirection} + final TextDirection? textDirection; + + /// {@macro flutter.painting.textPainter.textScaler} + final TextScaler? textScaler; + + /// {@macro flutter.widgets.editableText.autofocus} + final bool autofocus; + + /// {@macro flutter.widgets.editableText.minLines} + final int? minLines; + + /// {@macro flutter.widgets.editableText.maxLines} + final int? maxLines; + + /// {@macro flutter.widgets.editableText.showCursor} + final bool showCursor; + + /// {@macro flutter.widgets.editableText.cursorWidth} + final double cursorWidth; + + /// {@macro flutter.widgets.editableText.cursorHeight} + final double? cursorHeight; + + /// {@macro flutter.widgets.editableText.cursorRadius} + final Radius? cursorRadius; + + /// The color of the cursor. + /// + /// The cursor indicates the current text insertion point. + /// + /// If null then [DefaultSelectionStyle.cursorColor] is used. If that is also + /// null and [ThemeData.platform] is [TargetPlatform.iOS] or + /// [TargetPlatform.macOS], then [CupertinoThemeData.primaryColor] is used. + /// Otherwise [ColorScheme.primary] of [ThemeData.colorScheme] is used. + final Color? cursorColor; + + /// Controls how tall the selection highlight boxes are computed to be. + /// + /// See [ui.BoxHeightStyle] for details on available styles. + final ui.BoxHeightStyle selectionHeightStyle; + + /// Controls how wide the selection highlight boxes are computed to be. + /// + /// See [ui.BoxWidthStyle] for details on available styles. + final ui.BoxWidthStyle selectionWidthStyle; + + /// {@macro flutter.widgets.editableText.enableInteractiveSelection} + final bool enableInteractiveSelection; + + /// {@macro flutter.widgets.editableText.selectionControls} + final TextSelectionControls? selectionControls; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + /// Configuration of toolbar options. + /// + /// Paste and cut will be disabled regardless. + /// + /// If not set, select all and copy will be enabled by default. + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + final ToolbarOptions? toolbarOptions; + + /// {@macro flutter.widgets.editableText.selectionEnabled} + bool get selectionEnabled => enableInteractiveSelection; + + /// Called when the user taps on this selectable text. + /// + /// The selectable text builds a [GestureDetector] to handle input events like tap, + /// to trigger focus requests, to move the caret, adjust the selection, etc. + /// Handling some of those events by wrapping the selectable text with a competing + /// GestureDetector is problematic. + /// + /// To unconditionally handle taps, without interfering with the selectable text's + /// internal gesture detector, provide this callback. + /// + /// To be notified when the text field gains or loses the focus, provide a + /// [focusNode] and add a listener to that. + /// + /// To listen to arbitrary pointer events without competing with the + /// selectable text's internal gesture detector, use a [Listener]. + final GestureTapCallback? onTap; + + /// {@macro flutter.widgets.editableText.scrollPhysics} + final ScrollPhysics? scrollPhysics; + + /// {@macro flutter.widgets.Text.semanticsLabel} + final String? semanticsLabel; + + /// {@macro dart.ui.textHeightBehavior} + final TextHeightBehavior? textHeightBehavior; + + /// {@macro flutter.painting.textPainter.textWidthBasis} + final TextWidthBasis? textWidthBasis; + + /// {@macro flutter.widgets.editableText.onSelectionChanged} + final SelectionChangedCallback? onSelectionChanged; + + /// {@macro flutter.widgets.EditableText.contextMenuBuilder} + final EditableTextContextMenuBuilder? contextMenuBuilder; + + static Widget _defaultContextMenuBuilder( + BuildContext context, EditableTextState editableTextState) { + return AdaptiveTextSelectionToolbar.editableText( + editableTextState: editableTextState, + ); + } + + /// The configuration for the magnifier used when the text is selected. + /// + /// By default, builds a [CupertinoTextMagnifier] on iOS and [TextMagnifier] + /// on Android, and builds nothing on all other platforms. To suppress the + /// magnifier, consider passing [TextMagnifierConfiguration.disabled]. + /// + /// {@macro flutter.widgets.magnifier.intro} + final TextMagnifierConfiguration? magnifierConfiguration; + + @override + State<_SelectableText> createState() => _SelectableTextState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + .add(DiagnosticsProperty('data', data, defaultValue: null)); + properties.add(DiagnosticsProperty('semanticsLabel', semanticsLabel, + defaultValue: null)); + properties.add(DiagnosticsProperty('focusNode', focusNode, + defaultValue: null)); + properties.add( + DiagnosticsProperty('style', style, defaultValue: null)); + properties.add( + DiagnosticsProperty('autofocus', autofocus, defaultValue: false)); + properties.add(DiagnosticsProperty('showCursor', showCursor, + defaultValue: false)); + properties.add(IntProperty('minLines', minLines, defaultValue: null)); + properties.add(IntProperty('maxLines', maxLines, defaultValue: null)); + properties.add( + EnumProperty('textAlign', textAlign, defaultValue: null)); + properties.add(EnumProperty('textDirection', textDirection, + defaultValue: null)); + properties.add(DiagnosticsProperty('textScaler', textScaler, + defaultValue: null)); + properties + .add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0)); + properties + .add(DoubleProperty('cursorHeight', cursorHeight, defaultValue: null)); + properties.add(DiagnosticsProperty('cursorRadius', cursorRadius, + defaultValue: null)); + properties.add(DiagnosticsProperty('cursorColor', cursorColor, + defaultValue: null)); + properties.add(FlagProperty('selectionEnabled', + value: selectionEnabled, + defaultValue: true, + ifFalse: 'selection disabled')); + properties.add(DiagnosticsProperty( + 'selectionControls', selectionControls, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'scrollPhysics', scrollPhysics, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'textHeightBehavior', textHeightBehavior, + defaultValue: null)); + } +} + +class _SelectableTextState extends State<_SelectableText> + // zmtzawqlp + implements + _TextSelectionGestureDetectorBuilderDelegate { + // zmtzawqlp + _EditableTextState? get _editableText => editableTextKey.currentState; + + late _TextSpanEditingController _controller; + + FocusNode? _focusNode; + FocusNode get _effectiveFocusNode => + widget.focusNode ?? (_focusNode ??= FocusNode(skipTraversal: true)); + + bool _showSelectionHandles = false; + + late _SelectableTextSelectionGestureDetectorBuilder + _selectionGestureDetectorBuilder; + + // API for TextSelectionGestureDetectorBuilderDelegate. + @override + late bool forcePressEnabled; + + // zmtzawqlp + @override + final GlobalKey<_EditableTextState> editableTextKey = + GlobalKey<_EditableTextState>(); + + @override + bool get selectionEnabled => widget.selectionEnabled; + // End of API for TextSelectionGestureDetectorBuilderDelegate. + + @override + void initState() { + super.initState(); + _selectionGestureDetectorBuilder = + _SelectableTextSelectionGestureDetectorBuilder( + state: this, + ); + _controller = _TextSpanEditingController( + textSpan: widget.textSpan ?? TextSpan(text: widget.data), + ); + _controller.addListener(_onControllerChanged); + } + + @override + void didUpdateWidget(_SelectableText oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.data != oldWidget.data || + widget.textSpan != oldWidget.textSpan) { + _controller.removeListener(_onControllerChanged); + _controller.dispose(); + _controller = _TextSpanEditingController( + textSpan: widget.textSpan ?? TextSpan(text: widget.data), + ); + _controller.addListener(_onControllerChanged); + } + if (_effectiveFocusNode.hasFocus && _controller.selection.isCollapsed) { + _showSelectionHandles = false; + } else { + _showSelectionHandles = true; + } + } + + @override + void dispose() { + _focusNode?.dispose(); + _controller.dispose(); + super.dispose(); + } + + void _onControllerChanged() { + final bool showSelectionHandles = + !_effectiveFocusNode.hasFocus || !_controller.selection.isCollapsed; + if (showSelectionHandles == _showSelectionHandles) { + return; + } + setState(() { + _showSelectionHandles = showSelectionHandles; + }); + } + + void _handleSelectionChanged( + TextSelection selection, SelectionChangedCause? cause) { + final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause); + if (willShowSelectionHandles != _showSelectionHandles) { + setState(() { + _showSelectionHandles = willShowSelectionHandles; + }); + } + + widget.onSelectionChanged?.call(selection, cause); + + switch (Theme.of(context).platform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + if (cause == SelectionChangedCause.longPress) { + _editableText?.bringIntoView(selection.base); + } + return; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + // Do nothing. + } + } + + /// Toggle the toolbar when a selection handle is tapped. + void _handleSelectionHandleTapped() { + if (_controller.selection.isCollapsed) { + _editableText!.toggleToolbar(); + } + } + + bool _shouldShowSelectionHandles(SelectionChangedCause? cause) { + // When the text field is activated by something that doesn't trigger the + // selection overlay, we shouldn't show the handles either. + if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar) { + return false; + } + + if (_controller.selection.isCollapsed) { + return false; + } + + if (cause == SelectionChangedCause.keyboard) { + return false; + } + + if (cause == SelectionChangedCause.longPress) { + return true; + } + + if (_controller.text.isNotEmpty) { + return true; + } + + return false; + } + + @override + Widget build(BuildContext context) { + // TODO(garyq): Assert to block WidgetSpans from being used here are removed, + // but we still do not yet have nice handling of things like carets, clipboard, + // and other features. We should add proper support. Currently, caret handling + // is blocked on SkParagraph switch and https://github.com/flutter/engine/pull/27010 + // should be landed in SkParagraph after the switch is complete. + assert(debugCheckHasMediaQuery(context)); + assert(debugCheckHasDirectionality(context)); + assert( + !(widget.style != null && + !widget.style!.inherit && + (widget.style!.fontSize == null || + widget.style!.textBaseline == null)), + 'inherit false style must supply fontSize and textBaseline', + ); + + final ThemeData theme = Theme.of(context); + final DefaultSelectionStyle selectionStyle = + DefaultSelectionStyle.of(context); + final FocusNode focusNode = _effectiveFocusNode; + + TextSelectionControls? textSelectionControls = widget.selectionControls; + final bool paintCursorAboveText; + final bool cursorOpacityAnimates; + Offset? cursorOffset; + final Color cursorColor; + final Color selectionColor; + Radius? cursorRadius = widget.cursorRadius; + + switch (theme.platform) { + case TargetPlatform.iOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + forcePressEnabled = true; + textSelectionControls ??= cupertinoTextSelectionHandleControls; + paintCursorAboveText = true; + cursorOpacityAnimates = true; + cursorColor = widget.cursorColor ?? + selectionStyle.cursorColor ?? + cupertinoTheme.primaryColor; + selectionColor = selectionStyle.selectionColor ?? + cupertinoTheme.primaryColor.withOpacity(0.40); + cursorRadius ??= const Radius.circular(2.0); + cursorOffset = Offset( + iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0); + + case TargetPlatform.macOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + forcePressEnabled = false; + textSelectionControls ??= cupertinoDesktopTextSelectionHandleControls; + paintCursorAboveText = true; + cursorOpacityAnimates = true; + cursorColor = widget.cursorColor ?? + selectionStyle.cursorColor ?? + cupertinoTheme.primaryColor; + selectionColor = selectionStyle.selectionColor ?? + cupertinoTheme.primaryColor.withOpacity(0.40); + cursorRadius ??= const Radius.circular(2.0); + cursorOffset = Offset( + iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0); + + case TargetPlatform.android: + case TargetPlatform.fuchsia: + forcePressEnabled = false; + textSelectionControls ??= materialTextSelectionHandleControls; + paintCursorAboveText = false; + cursorOpacityAnimates = false; + cursorColor = widget.cursorColor ?? + selectionStyle.cursorColor ?? + theme.colorScheme.primary; + selectionColor = selectionStyle.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + + case TargetPlatform.linux: + case TargetPlatform.windows: + forcePressEnabled = false; + textSelectionControls ??= desktopTextSelectionHandleControls; + paintCursorAboveText = false; + cursorOpacityAnimates = false; + cursorColor = widget.cursorColor ?? + selectionStyle.cursorColor ?? + theme.colorScheme.primary; + selectionColor = selectionStyle.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + } + + final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context); + TextStyle? effectiveTextStyle = widget.style; + if (effectiveTextStyle == null || effectiveTextStyle.inherit) { + effectiveTextStyle = defaultTextStyle.style + .merge(widget.style ?? _controller._textSpan.style); + } + final Widget child = RepaintBoundary( + // zmtzawqlp + child: _EditableText( + key: editableTextKey, + style: effectiveTextStyle, + readOnly: true, + toolbarOptions: widget.toolbarOptions, + textWidthBasis: + widget.textWidthBasis ?? defaultTextStyle.textWidthBasis, + textHeightBehavior: + widget.textHeightBehavior ?? defaultTextStyle.textHeightBehavior, + showSelectionHandles: _showSelectionHandles, + showCursor: widget.showCursor, + controller: _controller, + focusNode: focusNode, + strutStyle: widget.strutStyle ?? const StrutStyle(), + textAlign: + widget.textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start, + textDirection: widget.textDirection, + textScaler: widget.textScaler, + autofocus: widget.autofocus, + forceLine: false, + minLines: widget.minLines, + maxLines: widget.maxLines ?? defaultTextStyle.maxLines, + selectionColor: selectionColor, + selectionControls: + widget.selectionEnabled ? textSelectionControls : null, + onSelectionChanged: _handleSelectionChanged, + onSelectionHandleTapped: _handleSelectionHandleTapped, + rendererIgnoresPointer: true, + cursorWidth: widget.cursorWidth, + cursorHeight: widget.cursorHeight, + cursorRadius: cursorRadius, + cursorColor: cursorColor, + selectionHeightStyle: widget.selectionHeightStyle, + selectionWidthStyle: widget.selectionWidthStyle, + cursorOpacityAnimates: cursorOpacityAnimates, + cursorOffset: cursorOffset, + paintCursorAboveText: paintCursorAboveText, + backgroundCursorColor: CupertinoColors.inactiveGray, + enableInteractiveSelection: widget.enableInteractiveSelection, + magnifierConfiguration: widget.magnifierConfiguration ?? + TextMagnifier.adaptiveMagnifierConfiguration, + dragStartBehavior: widget.dragStartBehavior, + scrollPhysics: widget.scrollPhysics, + autofillHints: null, + contextMenuBuilder: widget.contextMenuBuilder, + ), + ); + + return Semantics( + label: widget.semanticsLabel, + excludeSemantics: widget.semanticsLabel != null, + onLongPress: () { + _effectiveFocusNode.requestFocus(); + }, + child: _selectionGestureDetectorBuilder.buildGestureDetector( + behavior: HitTestBehavior.translucent, + child: child, + ), + ); + } +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/official/rendering/editable.dart b/local_packages/extended_text_field-16.0.2/lib/src/official/rendering/editable.dart new file mode 100644 index 0000000..0e5df9f --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/official/rendering/editable.dart @@ -0,0 +1,3136 @@ +part of 'package:extended_text_field/src/extended/widgets/text_field.dart'; + +const double _kCaretGap = 1.0; // pixels +const double _kCaretHeightOffset = 2.0; // pixels + +// The additional size on the x and y axis with which to expand the prototype +// cursor to render the floating cursor in pixels. +const EdgeInsets _kFloatingCursorSizeIncrease = + EdgeInsets.symmetric(horizontal: 0.5, vertical: 1.0); + +// The corner radius of the floating cursor in pixels. +const Radius _kFloatingCursorRadius = Radius.circular(1.0); +// This constant represents the shortest squared distance required between the floating cursor +// and the regular cursor when both are present in the text field. +// If the squared distance between the two cursors is less than this value, +// it's not necessary to display both cursors at the same time. +// This behavior is consistent with the one observed in iOS UITextField. +const double _kShortestDistanceSquaredWithFloatingAndRegularCursors = + 15.0 * 15.0; + +/// The consecutive sequence of [TextPosition]s that the caret should move to +/// when the user navigates the paragraph using the upward arrow key or the +/// downward arrow key. +/// +/// {@template flutter.rendering.RenderEditable.verticalArrowKeyMovement} +/// When the user presses the upward arrow key or the downward arrow key, on +/// many platforms (macOS for instance), the caret will move to the previous +/// line or the next line, while maintaining its original horizontal location. +/// When it encounters a shorter line, the caret moves to the closest horizontal +/// location within that line, and restores the original horizontal location +/// when a long enough line is encountered. +/// +/// Additionally, the caret will move to the beginning of the document if the +/// upward arrow key is pressed and the caret is already on the first line. If +/// the downward arrow key is pressed next, the caret will restore its original +/// horizontal location and move to the second line. Similarly the caret moves +/// to the end of the document if the downward arrow key is pressed when it's +/// already on the last line. +/// +/// Consider a left-aligned paragraph: +/// aa| +/// a +/// aaa +/// where the caret was initially placed at the end of the first line. Pressing +/// the downward arrow key once will move the caret to the end of the second +/// line, and twice the arrow key moves to the third line after the second "a" +/// on that line. Pressing the downward arrow key again, the caret will move to +/// the end of the third line (the end of the document). Pressing the upward +/// arrow key in this state will result in the caret moving to the end of the +/// second line. +/// +/// Vertical caret runs are typically interrupted when the layout of the text +/// changes (including when the text itself changes), or when the selection is +/// changed by other input events or programmatically (for example, when the +/// user pressed the left arrow key). +/// {@endtemplate} +/// +/// The [movePrevious] method moves the caret location (which is +/// [VerticalCaretMovementRun.current]) to the previous line, and in case +/// the caret is already on the first line, the method does nothing and returns +/// false. Similarly the [moveNext] method moves the caret to the next line, and +/// returns false if the caret is already on the last line. +/// +/// The [moveByOffset] method takes a pixel offset from the current position to move +/// the caret up or down. +/// +/// If the underlying paragraph's layout changes, [isValid] becomes false and +/// the [VerticalCaretMovementRun] must not be used. The [isValid] property must +/// be checked before calling [movePrevious], [moveNext] and [moveByOffset], +/// or accessing [current]. +class VerticalCaretMovementRun implements Iterator { + VerticalCaretMovementRun._( + this._editable, + this._lineMetrics, + this._currentTextPosition, + this._currentLine, + this._currentOffset, + ); + + Offset _currentOffset; + int _currentLine; + TextPosition _currentTextPosition; + + final List _lineMetrics; + // zmtzawqlp + final _RenderEditable _editable; + + bool _isValid = true; + + /// Whether this [VerticalCaretMovementRun] can still continue. + /// + /// A [VerticalCaretMovementRun] run is valid if the underlying text layout + /// hasn't changed. + /// + /// The [current] value and the [movePrevious], [moveNext] and [moveByOffset] + /// methods must not be accessed when [isValid] is false. + bool get isValid { + if (!_isValid) { + return false; + } + final List newLineMetrics = + _editable._textPainter.computeLineMetrics(); + // Use the implementation detail of the computeLineMetrics method to figure + // out if the current text layout has been invalidated. + if (!identical(newLineMetrics, _lineMetrics)) { + _isValid = false; + } + return _isValid; + } + + final Map> _positionCache = + >{}; + + MapEntry _getTextPositionForLine(int lineNumber) { + assert(isValid); + assert(lineNumber >= 0); + final MapEntry? cachedPosition = + _positionCache[lineNumber]; + if (cachedPosition != null) { + return cachedPosition; + } + assert(lineNumber != _currentLine); + + final Offset newOffset = + Offset(_currentOffset.dx, _lineMetrics[lineNumber].baseline); + final TextPosition closestPosition = + _editable._textPainter.getPositionForOffset(newOffset); + final MapEntry position = + MapEntry(newOffset, closestPosition); + _positionCache[lineNumber] = position; + return position; + } + + @override + TextPosition get current { + assert(isValid); + return _currentTextPosition; + } + + @override + bool moveNext() { + assert(isValid); + if (_currentLine + 1 >= _lineMetrics.length) { + return false; + } + final MapEntry position = + _getTextPositionForLine(_currentLine + 1); + _currentLine += 1; + _currentOffset = position.key; + _currentTextPosition = position.value; + return true; + } + + /// Move back to the previous element. + /// + /// Returns true and updates [current] if successful. + bool movePrevious() { + assert(isValid); + if (_currentLine <= 0) { + return false; + } + final MapEntry position = + _getTextPositionForLine(_currentLine - 1); + _currentLine -= 1; + _currentOffset = position.key; + _currentTextPosition = position.value; + return true; + } + + /// Move forward or backward by a number of elements determined + /// by pixel [offset]. + /// + /// If [offset] is negative, move backward; otherwise move forward. + /// + /// Returns true and updates [current] if successful. + bool moveByOffset(double offset) { + final Offset initialOffset = _currentOffset; + if (offset >= 0.0) { + while (_currentOffset.dy < initialOffset.dy + offset) { + if (!moveNext()) { + break; + } + } + } else { + while (_currentOffset.dy > initialOffset.dy + offset) { + if (!movePrevious()) { + break; + } + } + } + return initialOffset != _currentOffset; + } +} + +/// [RenderEditable] +/// Displays some text in a scrollable container with a potentially blinking +/// cursor and with gesture recognizers. +/// +/// This is the renderer for an editable text field. It does not directly +/// provide affordances for editing the text, but it does handle text selection +/// and manipulation of the text cursor. +/// +/// The [text] is displayed, scrolled by the given [offset], aligned according +/// to [textAlign]. The [maxLines] property controls whether the text displays +/// on one line or many. The [selection], if it is not collapsed, is painted in +/// the [selectionColor]. If it _is_ collapsed, then it represents the cursor +/// position. The cursor is shown while [showCursor] is true. It is painted in +/// the [cursorColor]. +/// +/// Keyboard handling, IME handling, scrolling, toggling the [showCursor] value +/// to actually blink the cursor, and other features not mentioned above are the +/// responsibility of higher layers and not handled by this object. +class _RenderEditable extends RenderBox + with + RelayoutWhenSystemFontsChangeMixin, + ContainerRenderObjectMixin, + RenderInlineChildrenContainerDefaults + implements TextLayoutMetrics { + /// Creates a render object that implements the visual aspects of a text field. + /// + /// The [textAlign] argument defaults to [TextAlign.start]. + /// + /// If [showCursor] is not specified, then it defaults to hiding the cursor. + /// + /// The [maxLines] property can be set to null to remove the restriction on + /// the number of lines. By default, it is 1, meaning this is a single-line + /// text field. If it is not null, it must be greater than zero. + /// + /// Use [ViewportOffset.zero] for the [offset] if there is no need for + /// scrolling. + _RenderEditable({ + InlineSpan? text, + required TextDirection textDirection, + TextAlign textAlign = TextAlign.start, + Color? cursorColor, + Color? backgroundCursorColor, + ValueNotifier? showCursor, + bool? hasFocus, + required LayerLink startHandleLayerLink, + required LayerLink endHandleLayerLink, + int? maxLines = 1, + int? minLines, + bool expands = false, + StrutStyle? strutStyle, + Color? selectionColor, + TextScaler textScaler = TextScaler.noScaling, + TextSelection? selection, + required ViewportOffset offset, + this.ignorePointer = false, + bool readOnly = false, + bool forceLine = true, + TextHeightBehavior? textHeightBehavior, + TextWidthBasis textWidthBasis = TextWidthBasis.parent, + String obscuringCharacter = '•', + bool obscureText = false, + Locale? locale, + double cursorWidth = 1.0, + double? cursorHeight, + Radius? cursorRadius, + bool paintCursorAboveText = false, + Offset cursorOffset = Offset.zero, + double devicePixelRatio = 1.0, + ui.BoxHeightStyle selectionHeightStyle = ui.BoxHeightStyle.tight, + ui.BoxWidthStyle selectionWidthStyle = ui.BoxWidthStyle.tight, + bool? enableInteractiveSelection, + this.floatingCursorAddedMargin = const EdgeInsets.fromLTRB(4, 4, 4, 5), + TextRange? promptRectRange, + Color? promptRectColor, + Clip clipBehavior = Clip.hardEdge, + required this.textSelectionDelegate, + RenderEditablePainter? painter, + RenderEditablePainter? foregroundPainter, + List? children, + }) : assert(maxLines == null || maxLines > 0), + assert(minLines == null || minLines > 0), + assert( + (maxLines == null) || (minLines == null) || (maxLines >= minLines), + "minLines can't be greater than maxLines", + ), + assert( + !expands || (maxLines == null && minLines == null), + 'minLines and maxLines must be null when expands is true.', + ), + assert(obscuringCharacter.characters.length == 1), + assert(cursorWidth >= 0.0), + assert(cursorHeight == null || cursorHeight >= 0.0), + _textPainter = TextPainter( + text: text, + textAlign: textAlign, + textDirection: textDirection, + textScaler: textScaler, + locale: locale, + maxLines: maxLines == 1 ? 1 : null, + strutStyle: strutStyle, + textHeightBehavior: textHeightBehavior, + textWidthBasis: textWidthBasis, + ), + _showCursor = showCursor ?? ValueNotifier(false), + _maxLines = maxLines, + _minLines = minLines, + _expands = expands, + _selection = selection, + _offset = offset, + _cursorWidth = cursorWidth, + _cursorHeight = cursorHeight, + _paintCursorOnTop = paintCursorAboveText, + _enableInteractiveSelection = enableInteractiveSelection, + _devicePixelRatio = devicePixelRatio, + _startHandleLayerLink = startHandleLayerLink, + _endHandleLayerLink = endHandleLayerLink, + _obscuringCharacter = obscuringCharacter, + _obscureText = obscureText, + _readOnly = readOnly, + _forceLine = forceLine, + _clipBehavior = clipBehavior, + _hasFocus = hasFocus ?? false, + _disposeShowCursor = showCursor == null { + assert(!_showCursor.value || cursorColor != null); + + _selectionPainter.highlightColor = selectionColor; + _selectionPainter.highlightedRange = selection; + _selectionPainter.selectionHeightStyle = selectionHeightStyle; + _selectionPainter.selectionWidthStyle = selectionWidthStyle; + + _autocorrectHighlightPainter.highlightColor = promptRectColor; + _autocorrectHighlightPainter.highlightedRange = promptRectRange; + + _caretPainter.caretColor = cursorColor; + _caretPainter.cursorRadius = cursorRadius; + _caretPainter.cursorOffset = cursorOffset; + _caretPainter.backgroundCursorColor = backgroundCursorColor; + + _updateForegroundPainter(foregroundPainter); + _updatePainter(painter); + addAll(children); + } + + /// Child render objects + _RenderEditableCustomPaint? _foregroundRenderObject; + _RenderEditableCustomPaint? _backgroundRenderObject; + + @override + void dispose() { + _leaderLayerHandler.layer = null; + _foregroundRenderObject?.dispose(); + _foregroundRenderObject = null; + _backgroundRenderObject?.dispose(); + _backgroundRenderObject = null; + _clipRectLayer.layer = null; + _cachedBuiltInForegroundPainters?.dispose(); + _cachedBuiltInPainters?.dispose(); + _selectionStartInViewport.dispose(); + _selectionEndInViewport.dispose(); + _autocorrectHighlightPainter.dispose(); + _selectionPainter.dispose(); + _caretPainter.dispose(); + _textPainter.dispose(); + _textIntrinsicsCache?.dispose(); + if (_disposeShowCursor) { + _showCursor.dispose(); + _disposeShowCursor = false; + } + super.dispose(); + } + + void _updateForegroundPainter(RenderEditablePainter? newPainter) { + final _CompositeRenderEditablePainter effectivePainter = newPainter == null + ? _builtInForegroundPainters + : _CompositeRenderEditablePainter(painters: [ + _builtInForegroundPainters, + newPainter, + ]); + + if (_foregroundRenderObject == null) { + final _RenderEditableCustomPaint foregroundRenderObject = + _RenderEditableCustomPaint(painter: effectivePainter); + adoptChild(foregroundRenderObject); + _foregroundRenderObject = foregroundRenderObject; + } else { + _foregroundRenderObject?.painter = effectivePainter; + } + _foregroundPainter = newPainter; + } + + /// The [RenderEditablePainter] to use for painting above this + /// [RenderEditable]'s text content. + /// + /// The new [RenderEditablePainter] will replace the previously specified + /// foreground painter, and schedule a repaint if the new painter's + /// `shouldRepaint` method returns true. + RenderEditablePainter? get foregroundPainter => _foregroundPainter; + RenderEditablePainter? _foregroundPainter; + set foregroundPainter(RenderEditablePainter? newPainter) { + if (newPainter == _foregroundPainter) { + return; + } + _updateForegroundPainter(newPainter); + } + + void _updatePainter(RenderEditablePainter? newPainter) { + final _CompositeRenderEditablePainter effectivePainter = newPainter == null + ? _builtInPainters + : _CompositeRenderEditablePainter( + painters: [_builtInPainters, newPainter]); + + if (_backgroundRenderObject == null) { + final _RenderEditableCustomPaint backgroundRenderObject = + _RenderEditableCustomPaint(painter: effectivePainter); + adoptChild(backgroundRenderObject); + _backgroundRenderObject = backgroundRenderObject; + } else { + _backgroundRenderObject?.painter = effectivePainter; + } + _painter = newPainter; + } + + /// Sets the [RenderEditablePainter] to use for painting beneath this + /// [RenderEditable]'s text content. + /// + /// The new [RenderEditablePainter] will replace the previously specified + /// painter, and schedule a repaint if the new painter's `shouldRepaint` + /// method returns true. + RenderEditablePainter? get painter => _painter; + RenderEditablePainter? _painter; + set painter(RenderEditablePainter? newPainter) { + if (newPainter == _painter) { + return; + } + _updatePainter(newPainter); + } + + // Caret Painters: + // A single painter for both the regular caret and the floating cursor. + late final _CaretPainter _caretPainter = _CaretPainter(); + + // Text Highlight painters: + final _TextHighlightPainter _selectionPainter = _TextHighlightPainter(); + final _TextHighlightPainter _autocorrectHighlightPainter = + _TextHighlightPainter(); + + _CompositeRenderEditablePainter get _builtInForegroundPainters => + _cachedBuiltInForegroundPainters ??= _createBuiltInForegroundPainters(); + _CompositeRenderEditablePainter? _cachedBuiltInForegroundPainters; + _CompositeRenderEditablePainter _createBuiltInForegroundPainters() { + return _CompositeRenderEditablePainter( + painters: [ + if (paintCursorAboveText) _caretPainter, + ], + ); + } + + _CompositeRenderEditablePainter get _builtInPainters => + _cachedBuiltInPainters ??= _createBuiltInPainters(); + _CompositeRenderEditablePainter? _cachedBuiltInPainters; + _CompositeRenderEditablePainter _createBuiltInPainters() { + return _CompositeRenderEditablePainter( + painters: [ + _autocorrectHighlightPainter, + _selectionPainter, + if (!paintCursorAboveText) _caretPainter, + ], + ); + } + + /// Whether the [handleEvent] will propagate pointer events to selection + /// handlers. + /// + /// If this property is true, the [handleEvent] assumes that this renderer + /// will be notified of input gestures via [handleTapDown], [handleTap], + /// [handleDoubleTap], and [handleLongPress]. + /// + /// If there are any gesture recognizers in the text span, the [handleEvent] + /// will still propagate pointer events to those recognizers. + /// + /// The default value of this property is false. + bool ignorePointer; + + /// {@macro dart.ui.textHeightBehavior} + TextHeightBehavior? get textHeightBehavior => _textPainter.textHeightBehavior; + set textHeightBehavior(TextHeightBehavior? value) { + if (_textPainter.textHeightBehavior == value) { + return; + } + _textPainter.textHeightBehavior = value; + markNeedsLayout(); + } + + /// {@macro flutter.painting.textPainter.textWidthBasis} + TextWidthBasis get textWidthBasis => _textPainter.textWidthBasis; + set textWidthBasis(TextWidthBasis value) { + if (_textPainter.textWidthBasis == value) { + return; + } + _textPainter.textWidthBasis = value; + markNeedsLayout(); + } + + /// The pixel ratio of the current device. + /// + /// Should be obtained by querying MediaQuery for the devicePixelRatio. + double get devicePixelRatio => _devicePixelRatio; + double _devicePixelRatio; + set devicePixelRatio(double value) { + if (devicePixelRatio == value) { + return; + } + _devicePixelRatio = value; + markNeedsLayout(); + } + + /// Character used for obscuring text if [obscureText] is true. + /// + /// Must have a length of exactly one. + String get obscuringCharacter => _obscuringCharacter; + String _obscuringCharacter; + set obscuringCharacter(String value) { + if (_obscuringCharacter == value) { + return; + } + assert(value.characters.length == 1); + _obscuringCharacter = value; + markNeedsLayout(); + } + + /// Whether to hide the text being edited (e.g., for passwords). + bool get obscureText => _obscureText; + bool _obscureText; + set obscureText(bool value) { + if (_obscureText == value) { + return; + } + _obscureText = value; + _cachedAttributedValue = null; + markNeedsSemanticsUpdate(); + } + + /// Controls how tall the selection highlight boxes are computed to be. + /// + /// See [ui.BoxHeightStyle] for details on available styles. + ui.BoxHeightStyle get selectionHeightStyle => + _selectionPainter.selectionHeightStyle; + set selectionHeightStyle(ui.BoxHeightStyle value) { + _selectionPainter.selectionHeightStyle = value; + } + + /// Controls how wide the selection highlight boxes are computed to be. + /// + /// See [ui.BoxWidthStyle] for details on available styles. + ui.BoxWidthStyle get selectionWidthStyle => + _selectionPainter.selectionWidthStyle; + set selectionWidthStyle(ui.BoxWidthStyle value) { + _selectionPainter.selectionWidthStyle = value; + } + + /// The object that controls the text selection, used by this render object + /// for implementing cut, copy, and paste keyboard shortcuts. + /// + /// It will make cut, copy and paste functionality work with the most recently + /// set [TextSelectionDelegate]. + TextSelectionDelegate textSelectionDelegate; + + /// Track whether position of the start of the selected text is within the viewport. + /// + /// For example, if the text contains "Hello World", and the user selects + /// "Hello", then scrolls so only "World" is visible, this will become false. + /// If the user scrolls back so that the "H" is visible again, this will + /// become true. + /// + /// This bool indicates whether the text is scrolled so that the handle is + /// inside the text field viewport, as opposed to whether it is actually + /// visible on the screen. + ValueListenable get selectionStartInViewport => + _selectionStartInViewport; + final ValueNotifier _selectionStartInViewport = + ValueNotifier(true); + + /// Track whether position of the end of the selected text is within the viewport. + /// + /// For example, if the text contains "Hello World", and the user selects + /// "World", then scrolls so only "Hello" is visible, this will become + /// 'false'. If the user scrolls back so that the "d" is visible again, this + /// will become 'true'. + /// + /// This bool indicates whether the text is scrolled so that the handle is + /// inside the text field viewport, as opposed to whether it is actually + /// visible on the screen. + ValueListenable get selectionEndInViewport => _selectionEndInViewport; + final ValueNotifier _selectionEndInViewport = ValueNotifier(true); + + /// Returns the TextPosition above or below the given offset. + TextPosition _getTextPositionVertical( + TextPosition position, double verticalOffset) { + final Offset caretOffset = + _textPainter.getOffsetForCaret(position, _caretPrototype); + final Offset caretOffsetTranslated = + caretOffset.translate(0.0, verticalOffset); + return _textPainter.getPositionForOffset(caretOffsetTranslated); + } + + // Start TextLayoutMetrics. + + /// {@macro flutter.services.TextLayoutMetrics.getLineAtOffset} + @override + TextSelection getLineAtOffset(TextPosition position) { + final TextRange line = _textPainter.getLineBoundary(position); + // If text is obscured, the entire string should be treated as one line. + if (obscureText) { + return TextSelection(baseOffset: 0, extentOffset: plainText.length); + } + return TextSelection(baseOffset: line.start, extentOffset: line.end); + } + + /// {@macro flutter.painting.TextPainter.getWordBoundary} + @override + TextRange getWordBoundary(TextPosition position) { + return _textPainter.getWordBoundary(position); + } + + /// {@macro flutter.services.TextLayoutMetrics.getTextPositionAbove} + @override + TextPosition getTextPositionAbove(TextPosition position) { + // The caret offset gives a location in the upper left hand corner of + // the caret so the middle of the line above is a half line above that + // point and the line below is 1.5 lines below that point. + final double preferredLineHeight = _textPainter.preferredLineHeight; + final double verticalOffset = -0.5 * preferredLineHeight; + return _getTextPositionVertical(position, verticalOffset); + } + + /// {@macro flutter.services.TextLayoutMetrics.getTextPositionBelow} + @override + TextPosition getTextPositionBelow(TextPosition position) { + // The caret offset gives a location in the upper left hand corner of + // the caret so the middle of the line above is a half line above that + // point and the line below is 1.5 lines below that point. + final double preferredLineHeight = _textPainter.preferredLineHeight; + final double verticalOffset = 1.5 * preferredLineHeight; + return _getTextPositionVertical(position, verticalOffset); + } + + // End TextLayoutMetrics. + + void _updateSelectionExtentsVisibility(Offset effectiveOffset) { + assert(selection != null); + if (!selection!.isValid) { + _selectionStartInViewport.value = false; + _selectionEndInViewport.value = false; + return; + } + final Rect visibleRegion = Offset.zero & size; + + final Offset startOffset = _textPainter.getOffsetForCaret( + TextPosition(offset: selection!.start, affinity: selection!.affinity), + _caretPrototype, + ); + // Check if the selection is visible with an approximation because a + // difference between rounded and unrounded values causes the caret to be + // reported as having a slightly (< 0.5) negative y offset. This rounding + // happens in paragraph.cc's layout and TextPainter's + // _applyFloatingPointHack. Ideally, the rounding mismatch will be fixed and + // this can be changed to be a strict check instead of an approximation. + const double visibleRegionSlop = 0.5; + _selectionStartInViewport.value = visibleRegion + .inflate(visibleRegionSlop) + .contains(startOffset + effectiveOffset); + + final Offset endOffset = _textPainter.getOffsetForCaret( + TextPosition(offset: selection!.end, affinity: selection!.affinity), + _caretPrototype, + ); + _selectionEndInViewport.value = visibleRegion + .inflate(visibleRegionSlop) + .contains(endOffset + effectiveOffset); + } + + void _setTextEditingValue( + TextEditingValue newValue, SelectionChangedCause cause) { + textSelectionDelegate.userUpdateTextEditingValue(newValue, cause); + } + + void _setSelection(TextSelection nextSelection, SelectionChangedCause cause) { + if (nextSelection.isValid) { + // The nextSelection is calculated based on plainText, which can be out + // of sync with the textSelectionDelegate.textEditingValue by one frame. + // This is due to the render editable and editable text handle pointer + // event separately. If the editable text changes the text during the + // event handler, the render editable will use the outdated text stored in + // the plainText when handling the pointer event. + // + // If this happens, we need to make sure the new selection is still valid. + final int textLength = textSelectionDelegate.textEditingValue.text.length; + nextSelection = nextSelection.copyWith( + baseOffset: math.min(nextSelection.baseOffset, textLength), + extentOffset: math.min(nextSelection.extentOffset, textLength), + ); + } + _setTextEditingValue( + textSelectionDelegate.textEditingValue.copyWith(selection: nextSelection), + cause, + ); + } + + @override + void markNeedsPaint() { + super.markNeedsPaint(); + // Tell the painters to repaint since text layout may have changed. + _foregroundRenderObject?.markNeedsPaint(); + _backgroundRenderObject?.markNeedsPaint(); + } + + @override + void systemFontsDidChange() { + super.systemFontsDidChange(); + _textPainter.markNeedsLayout(); + } + + /// Returns a plain text version of the text in [TextPainter]. + /// + /// If [obscureText] is true, returns the obscured text. See + /// [obscureText] and [obscuringCharacter]. + /// In order to get the styled text as an [InlineSpan] tree, use [text]. + String get plainText => _textPainter.plainText; + + /// The text to paint in the form of a tree of [InlineSpan]s. + /// + /// In order to get the plain text representation, use [plainText]. + InlineSpan? get text => _textPainter.text; + final TextPainter _textPainter; + AttributedString? _cachedAttributedValue; + List? _cachedCombinedSemanticsInfos; + set text(InlineSpan? value) { + if (_textPainter.text == value) { + return; + } + _cachedLineBreakCount = null; + _textPainter.text = value; + _cachedAttributedValue = null; + _cachedCombinedSemanticsInfos = null; + markNeedsLayout(); + markNeedsSemanticsUpdate(); + } + + TextPainter? _textIntrinsicsCache; + TextPainter get _textIntrinsics { + return (_textIntrinsicsCache ??= TextPainter()) + ..text = _textPainter.text + ..textAlign = _textPainter.textAlign + ..textDirection = _textPainter.textDirection + ..textScaler = _textPainter.textScaler + ..maxLines = _textPainter.maxLines + ..ellipsis = _textPainter.ellipsis + ..locale = _textPainter.locale + ..strutStyle = _textPainter.strutStyle + ..textWidthBasis = _textPainter.textWidthBasis + ..textHeightBehavior = _textPainter.textHeightBehavior; + } + + /// How the text should be aligned horizontally. + TextAlign get textAlign => _textPainter.textAlign; + set textAlign(TextAlign value) { + if (_textPainter.textAlign == value) { + return; + } + _textPainter.textAlign = value; + markNeedsLayout(); + } + + /// The directionality of the text. + /// + /// This decides how the [TextAlign.start], [TextAlign.end], and + /// [TextAlign.justify] values of [textAlign] are interpreted. + /// + /// This is also used to disambiguate how to render bidirectional text. For + /// example, if the [text] is an English phrase followed by a Hebrew phrase, + /// in a [TextDirection.ltr] context the English phrase will be on the left + /// and the Hebrew phrase to its right, while in a [TextDirection.rtl] + /// context, the English phrase will be on the right and the Hebrew phrase on + /// its left. + // TextPainter.textDirection is nullable, but it is set to a + // non-null value in the RenderEditable constructor and we refuse to + // set it to null here, so _textPainter.textDirection cannot be null. + TextDirection get textDirection => _textPainter.textDirection!; + set textDirection(TextDirection value) { + if (_textPainter.textDirection == value) { + return; + } + _textPainter.textDirection = value; + markNeedsLayout(); + markNeedsSemanticsUpdate(); + } + + /// Used by this renderer's internal [TextPainter] to select a locale-specific + /// font. + /// + /// In some cases the same Unicode character may be rendered differently depending + /// on the locale. For example the '骨' character is rendered differently in + /// the Chinese and Japanese locales. In these cases the [locale] may be used + /// to select a locale-specific font. + /// + /// If this value is null, a system-dependent algorithm is used to select + /// the font. + Locale? get locale => _textPainter.locale; + set locale(Locale? value) { + if (_textPainter.locale == value) { + return; + } + _textPainter.locale = value; + markNeedsLayout(); + } + + /// The [StrutStyle] used by the renderer's internal [TextPainter] to + /// determine the strut to use. + StrutStyle? get strutStyle => _textPainter.strutStyle; + set strutStyle(StrutStyle? value) { + if (_textPainter.strutStyle == value) { + return; + } + _textPainter.strutStyle = value; + markNeedsLayout(); + } + + /// The color to use when painting the cursor. + Color? get cursorColor => _caretPainter.caretColor; + set cursorColor(Color? value) { + _caretPainter.caretColor = value; + } + + /// The color to use when painting the cursor aligned to the text while + /// rendering the floating cursor. + /// + /// Typically this would be set to [CupertinoColors.inactiveGray]. + /// + /// If this is null, the background cursor is not painted. + /// + /// See also: + /// + /// * [FloatingCursorDragState], which explains the floating cursor feature + /// in detail. + Color? get backgroundCursorColor => _caretPainter.backgroundCursorColor; + set backgroundCursorColor(Color? value) { + _caretPainter.backgroundCursorColor = value; + } + + bool _disposeShowCursor; + + /// Whether to paint the cursor. + ValueNotifier get showCursor => _showCursor; + ValueNotifier _showCursor; + set showCursor(ValueNotifier value) { + if (_showCursor == value) { + return; + } + if (attached) { + _showCursor.removeListener(_showHideCursor); + } + if (_disposeShowCursor) { + _showCursor.dispose(); + _disposeShowCursor = false; + } + _showCursor = value; + if (attached) { + _showHideCursor(); + _showCursor.addListener(_showHideCursor); + } + } + + void _showHideCursor() { + _caretPainter.shouldPaint = showCursor.value; + } + + /// Whether the editable is currently focused. + bool get hasFocus => _hasFocus; + bool _hasFocus = false; + set hasFocus(bool value) { + if (_hasFocus == value) { + return; + } + _hasFocus = value; + markNeedsSemanticsUpdate(); + } + + /// Whether this rendering object will take a full line regardless the text width. + bool get forceLine => _forceLine; + bool _forceLine = false; + set forceLine(bool value) { + if (_forceLine == value) { + return; + } + _forceLine = value; + markNeedsLayout(); + } + + /// Whether this rendering object is read only. + bool get readOnly => _readOnly; + bool _readOnly = false; + set readOnly(bool value) { + if (_readOnly == value) { + return; + } + _readOnly = value; + markNeedsSemanticsUpdate(); + } + + /// The maximum number of lines for the text to span, wrapping if necessary. + /// + /// If this is 1 (the default), the text will not wrap, but will extend + /// indefinitely instead. + /// + /// If this is null, there is no limit to the number of lines. + /// + /// When this is not null, the intrinsic height of the render object is the + /// height of one line of text multiplied by this value. In other words, this + /// also controls the height of the actual editing widget. + int? get maxLines => _maxLines; + int? _maxLines; + + /// The value may be null. If it is not null, then it must be greater than zero. + set maxLines(int? value) { + assert(value == null || value > 0); + if (maxLines == value) { + return; + } + _maxLines = value; + + // Special case maxLines == 1 to keep only the first line so we can get the + // height of the first line in case there are hard line breaks in the text. + // See the `_preferredHeight` method. + _textPainter.maxLines = value == 1 ? 1 : null; + markNeedsLayout(); + } + + /// {@macro flutter.widgets.editableText.minLines} + int? get minLines => _minLines; + int? _minLines; + + /// The value may be null. If it is not null, then it must be greater than zero. + set minLines(int? value) { + assert(value == null || value > 0); + if (minLines == value) { + return; + } + _minLines = value; + markNeedsLayout(); + } + + /// {@macro flutter.widgets.editableText.expands} + bool get expands => _expands; + bool _expands; + set expands(bool value) { + if (expands == value) { + return; + } + _expands = value; + markNeedsLayout(); + } + + /// The color to use when painting the selection. + Color? get selectionColor => _selectionPainter.highlightColor; + set selectionColor(Color? value) { + _selectionPainter.highlightColor = value; + } + + /// The number of font pixels for each logical pixel. + /// + /// For example, if the text scale factor is 1.5, text will be 50% larger than + /// the specified font size. + + /// {@macro flutter.painting.textPainter.textScaler} + TextScaler get textScaler => _textPainter.textScaler; + set textScaler(TextScaler value) { + if (_textPainter.textScaler == value) { + return; + } + _textPainter.textScaler = value; + markNeedsLayout(); + } + + /// The region of text that is selected, if any. + /// + /// The caret position is represented by a collapsed selection. + /// + /// If [selection] is null, there is no selection and attempts to + /// manipulate the selection will throw. + TextSelection? get selection => _selection; + TextSelection? _selection; + set selection(TextSelection? value) { + if (_selection == value) { + return; + } + _selection = value; + _selectionPainter.highlightedRange = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + + /// The offset at which the text should be painted. + /// + /// If the text content is larger than the editable line itself, the editable + /// line clips the text. This property controls which part of the text is + /// visible by shifting the text by the given offset before clipping. + ViewportOffset get offset => _offset; + ViewportOffset _offset; + set offset(ViewportOffset value) { + if (_offset == value) { + return; + } + if (attached) { + _offset.removeListener(markNeedsPaint); + } + _offset = value; + if (attached) { + _offset.addListener(markNeedsPaint); + } + markNeedsLayout(); + } + + /// How thick the cursor will be. + double get cursorWidth => _cursorWidth; + double _cursorWidth = 1.0; + set cursorWidth(double value) { + if (_cursorWidth == value) { + return; + } + _cursorWidth = value; + markNeedsLayout(); + } + + /// How tall the cursor will be. + /// + /// This can be null, in which case the getter will actually return [preferredLineHeight]. + /// + /// Setting this to itself fixes the value to the current [preferredLineHeight]. Setting + /// this to null returns the behavior of deferring to [preferredLineHeight]. + // TODO(ianh): This is a confusing API. We should have a separate getter for the effective cursor height. + double get cursorHeight => _cursorHeight ?? preferredLineHeight; + double? _cursorHeight; + set cursorHeight(double? value) { + if (_cursorHeight == value) { + return; + } + _cursorHeight = value; + markNeedsLayout(); + } + + /// {@template flutter.rendering.RenderEditable.paintCursorAboveText} + /// If the cursor should be painted on top of the text or underneath it. + /// + /// By default, the cursor should be painted on top for iOS platforms and + /// underneath for Android platforms. + /// {@endtemplate} + bool get paintCursorAboveText => _paintCursorOnTop; + bool _paintCursorOnTop; + set paintCursorAboveText(bool value) { + if (_paintCursorOnTop == value) { + return; + } + _paintCursorOnTop = value; + // Clear cached built-in painters and reconfigure painters. + _cachedBuiltInForegroundPainters = null; + _cachedBuiltInPainters = null; + // Call update methods to rebuild and set the effective painters. + _updateForegroundPainter(_foregroundPainter); + _updatePainter(_painter); + } + + /// {@template flutter.rendering.RenderEditable.cursorOffset} + /// The offset that is used, in pixels, when painting the cursor on screen. + /// + /// By default, the cursor position should be set to an offset of + /// (-[cursorWidth] * 0.5, 0.0) on iOS platforms and (0, 0) on Android + /// platforms. The origin from where the offset is applied to is the arbitrary + /// location where the cursor ends up being rendered from by default. + /// {@endtemplate} + Offset get cursorOffset => _caretPainter.cursorOffset; + set cursorOffset(Offset value) { + _caretPainter.cursorOffset = value; + } + + /// How rounded the corners of the cursor should be. + /// + /// A null value is the same as [Radius.zero]. + Radius? get cursorRadius => _caretPainter.cursorRadius; + set cursorRadius(Radius? value) { + _caretPainter.cursorRadius = value; + } + + /// The [LayerLink] of start selection handle. + /// + /// [RenderEditable] is responsible for calculating the [Offset] of this + /// [LayerLink], which will be used as [CompositedTransformTarget] of start handle. + LayerLink get startHandleLayerLink => _startHandleLayerLink; + LayerLink _startHandleLayerLink; + set startHandleLayerLink(LayerLink value) { + if (_startHandleLayerLink == value) { + return; + } + _startHandleLayerLink = value; + markNeedsPaint(); + } + + /// The [LayerLink] of end selection handle. + /// + /// [RenderEditable] is responsible for calculating the [Offset] of this + /// [LayerLink], which will be used as [CompositedTransformTarget] of end handle. + LayerLink get endHandleLayerLink => _endHandleLayerLink; + LayerLink _endHandleLayerLink; + set endHandleLayerLink(LayerLink value) { + if (_endHandleLayerLink == value) { + return; + } + _endHandleLayerLink = value; + markNeedsPaint(); + } + + /// The padding applied to text field. Used to determine the bounds when + /// moving the floating cursor. + /// + /// Defaults to a padding with left, top and right set to 4, bottom to 5. + /// + /// See also: + /// + /// * [FloatingCursorDragState], which explains the floating cursor feature + /// in detail. + EdgeInsets floatingCursorAddedMargin; + + /// Returns true if the floating cursor is visible, false otherwise. + bool get floatingCursorOn => _floatingCursorOn; + bool _floatingCursorOn = false; + late TextPosition _floatingCursorTextPosition; + + /// Whether to allow the user to change the selection. + /// + /// Since [RenderEditable] does not handle selection manipulation + /// itself, this actually only affects whether the accessibility + /// hints provided to the system (via + /// [describeSemanticsConfiguration]) will enable selection + /// manipulation. It's the responsibility of this object's owner + /// to provide selection manipulation affordances. + /// + /// This field is used by [selectionEnabled] (which then controls + /// the accessibility hints mentioned above). When null, + /// [obscureText] is used to determine the value of + /// [selectionEnabled] instead. + bool? get enableInteractiveSelection => _enableInteractiveSelection; + bool? _enableInteractiveSelection; + set enableInteractiveSelection(bool? value) { + if (_enableInteractiveSelection == value) { + return; + } + _enableInteractiveSelection = value; + markNeedsLayout(); + markNeedsSemanticsUpdate(); + } + + /// Whether interactive selection are enabled based on the values of + /// [enableInteractiveSelection] and [obscureText]. + /// + /// Since [RenderEditable] does not handle selection manipulation + /// itself, this actually only affects whether the accessibility + /// hints provided to the system (via + /// [describeSemanticsConfiguration]) will enable selection + /// manipulation. It's the responsibility of this object's owner + /// to provide selection manipulation affordances. + /// + /// By default, [enableInteractiveSelection] is null, [obscureText] is false, + /// and this getter returns true. + /// + /// If [enableInteractiveSelection] is null and [obscureText] is true, then this + /// getter returns false. This is the common case for password fields. + /// + /// If [enableInteractiveSelection] is non-null then its value is + /// returned. An application might [enableInteractiveSelection] to + /// true to enable interactive selection for a password field, or to + /// false to unconditionally disable interactive selection. + bool get selectionEnabled { + return enableInteractiveSelection ?? !obscureText; + } + + /// The color used to paint the prompt rectangle. + /// + /// The prompt rectangle will only be requested on non-web iOS applications. + // TODO(ianh): We should change the getter to return null when _promptRectRange is null + // (otherwise, if you set it to null and then get it, you get back non-null). + // Alternatively, we could stop supporting setting this to null. + Color? get promptRectColor => _autocorrectHighlightPainter.highlightColor; + set promptRectColor(Color? newValue) { + _autocorrectHighlightPainter.highlightColor = newValue; + } + + /// Dismisses the currently displayed prompt rectangle and displays a new prompt rectangle + /// over [newRange] in the given color [promptRectColor]. + /// + /// The prompt rectangle will only be requested on non-web iOS applications. + /// + /// When set to null, the currently displayed prompt rectangle (if any) will be dismissed. + // ignore: use_setters_to_change_properties, (API predates enforcing the lint) + void setPromptRectRange(TextRange? newRange) { + _autocorrectHighlightPainter.highlightedRange = newRange; + } + + /// The maximum amount the text is allowed to scroll. + /// + /// This value is only valid after layout and can change as additional + /// text is entered or removed in order to accommodate expanding when + /// [expands] is set to true. + double get maxScrollExtent => _maxScrollExtent; + double _maxScrollExtent = 0; + + double get _caretMargin => _kCaretGap + cursorWidth; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge]. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.hardEdge; + set clipBehavior(Clip value) { + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + + /// Collected during [describeSemanticsConfiguration], used by + /// [assembleSemanticsNode] and [_combineSemanticsInfo]. + List? _semanticsInfo; + + // Caches [SemanticsNode]s created during [assembleSemanticsNode] so they + // can be re-used when [assembleSemanticsNode] is called again. This ensures + // stable ids for the [SemanticsNode]s of [TextSpan]s across + // [assembleSemanticsNode] invocations. + LinkedHashMap? _cachedChildNodes; + + /// Returns a list of rects that bound the given selection, and the text + /// direction. The text direction is used by the engine to calculate + /// the closest position to a given point. + /// + /// See [TextPainter.getBoxesForSelection] for more details. + List getBoxesForSelection(TextSelection selection) { + _computeTextMetricsIfNeeded(); + return _textPainter + .getBoxesForSelection(selection) + .map((TextBox textBox) => TextBox.fromLTRBD( + textBox.left + _paintOffset.dx, + textBox.top + _paintOffset.dy, + textBox.right + _paintOffset.dx, + textBox.bottom + _paintOffset.dy, + textBox.direction)) + .toList(); + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + _semanticsInfo = _textPainter.text!.getSemanticsInformation(); + // TODO(chunhtai): the macOS does not provide a public API to support text + // selections across multiple semantics nodes. Remove this platform check + // once we can support it. + // https://github.com/flutter/flutter/issues/77957 + if (_semanticsInfo!.any( + (InlineSpanSemanticsInformation info) => info.recognizer != null) && + defaultTargetPlatform != TargetPlatform.macOS) { + // TODO(zmtzawqlp): we support custom text span, so assert is not need here. + // assert(readOnly && !obscureText); + // For Selectable rich text with recognizer, we need to create a semantics + // node for each text fragment. + config + ..isSemanticBoundary = true + ..explicitChildNodes = true; + return; + } + if (_cachedAttributedValue == null) { + if (obscureText) { + _cachedAttributedValue = + AttributedString(obscuringCharacter * plainText.length); + } else { + final StringBuffer buffer = StringBuffer(); + int offset = 0; + final List attributes = []; + for (final InlineSpanSemanticsInformation info in _semanticsInfo!) { + final String label = info.semanticsLabel ?? info.text; + for (final StringAttribute infoAttribute in info.stringAttributes) { + final TextRange originalRange = infoAttribute.range; + attributes.add( + infoAttribute.copy( + range: TextRange( + start: offset + originalRange.start, + end: offset + originalRange.end), + ), + ); + } + buffer.write(label); + offset += label.length; + } + _cachedAttributedValue = + AttributedString(buffer.toString(), attributes: attributes); + } + } + config + ..attributedValue = _cachedAttributedValue! + ..isObscured = obscureText + ..isMultiline = _isMultiline + ..textDirection = textDirection + ..isFocused = hasFocus + ..isTextField = true + ..isReadOnly = readOnly; + + if (hasFocus && selectionEnabled) { + config.onSetSelection = _handleSetSelection; + } + + if (hasFocus && !readOnly) { + config.onSetText = _handleSetText; + } + + if (selectionEnabled && (selection?.isValid ?? false)) { + config.textSelection = selection; + if (_textPainter.getOffsetBefore(selection!.extentOffset) != null) { + config + ..onMoveCursorBackwardByWord = _handleMoveCursorBackwardByWord + ..onMoveCursorBackwardByCharacter = + _handleMoveCursorBackwardByCharacter; + } + if (_textPainter.getOffsetAfter(selection!.extentOffset) != null) { + config + ..onMoveCursorForwardByWord = _handleMoveCursorForwardByWord + ..onMoveCursorForwardByCharacter = + _handleMoveCursorForwardByCharacter; + } + } + } + + void _handleSetText(String text) { + textSelectionDelegate.userUpdateTextEditingValue( + TextEditingValue( + text: text, + selection: TextSelection.collapsed(offset: text.length), + ), + SelectionChangedCause.keyboard, + ); + } + + @override + void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, + Iterable children) { + assert(_semanticsInfo != null && _semanticsInfo!.isNotEmpty); + final List newChildren = []; + TextDirection currentDirection = textDirection; + Rect currentRect; + double ordinal = 0.0; + int start = 0; + int placeholderIndex = 0; + int childIndex = 0; + RenderBox? child = firstChild; + final LinkedHashMap newChildCache = + LinkedHashMap(); + _cachedCombinedSemanticsInfos ??= combineSemanticsInfo(_semanticsInfo!); + for (final InlineSpanSemanticsInformation info + in _cachedCombinedSemanticsInfos!) { + final TextSelection selection = TextSelection( + baseOffset: start, + extentOffset: start + info.text.length, + ); + start += info.text.length; + + if (info.isPlaceholder) { + // A placeholder span may have 0 to multiple semantics nodes, we need + // to annotate all of the semantics nodes belong to this span. + while (children.length > childIndex && + children + .elementAt(childIndex) + .isTagged(PlaceholderSpanIndexSemanticsTag(placeholderIndex))) { + final SemanticsNode childNode = children.elementAt(childIndex); + final TextParentData parentData = + child!.parentData! as TextParentData; + assert(parentData.offset != null); + newChildren.add(childNode); + childIndex += 1; + } + child = childAfter(child!); + placeholderIndex += 1; + } else { + final TextDirection initialDirection = currentDirection; + final List rects = + _textPainter.getBoxesForSelection(selection); + if (rects.isEmpty) { + continue; + } + Rect rect = rects.first.toRect(); + currentDirection = rects.first.direction; + for (final ui.TextBox textBox in rects.skip(1)) { + rect = rect.expandToInclude(textBox.toRect()); + currentDirection = textBox.direction; + } + // Any of the text boxes may have had infinite dimensions. + // We shouldn't pass infinite dimensions up to the bridges. + rect = Rect.fromLTWH( + math.max(0.0, rect.left), + math.max(0.0, rect.top), + math.min(rect.width, constraints.maxWidth), + math.min(rect.height, constraints.maxHeight), + ); + // Round the current rectangle to make this API testable and add some + // padding so that the accessibility rects do not overlap with the text. + currentRect = Rect.fromLTRB( + rect.left.floorToDouble() - 4.0, + rect.top.floorToDouble() - 4.0, + rect.right.ceilToDouble() + 4.0, + rect.bottom.ceilToDouble() + 4.0, + ); + final SemanticsConfiguration configuration = SemanticsConfiguration() + ..sortKey = OrdinalSortKey(ordinal++) + ..textDirection = initialDirection + ..attributedLabel = AttributedString(info.semanticsLabel ?? info.text, + attributes: info.stringAttributes); + switch (info.recognizer) { + case TapGestureRecognizer(onTap: final VoidCallback? onTap): + case DoubleTapGestureRecognizer( + onDoubleTap: final VoidCallback? onTap + ): + if (onTap != null) { + configuration.onTap = onTap; + configuration.isLink = true; + } + case LongPressGestureRecognizer( + onLongPress: final GestureLongPressCallback? onLongPress + ): + if (onLongPress != null) { + configuration.onLongPress = onLongPress; + } + case null: + break; + default: + assert(false, '${info.recognizer.runtimeType} is not supported.'); + } + if (node.parentPaintClipRect != null) { + final Rect paintRect = + node.parentPaintClipRect!.intersect(currentRect); + configuration.isHidden = paintRect.isEmpty && !currentRect.isEmpty; + } + late final SemanticsNode newChild; + if (_cachedChildNodes?.isNotEmpty ?? false) { + newChild = _cachedChildNodes!.remove(_cachedChildNodes!.keys.first)!; + } else { + final UniqueKey key = UniqueKey(); + newChild = SemanticsNode( + key: key, + showOnScreen: _createShowOnScreenFor(key), + ); + } + newChild + ..updateWith(config: configuration) + ..rect = currentRect; + newChildCache[newChild.key!] = newChild; + newChildren.add(newChild); + } + } + _cachedChildNodes = newChildCache; + node.updateWith(config: config, childrenInInversePaintOrder: newChildren); + } + + VoidCallback? _createShowOnScreenFor(Key key) { + return () { + final SemanticsNode node = _cachedChildNodes![key]!; + showOnScreen(descendant: this, rect: node.rect); + }; + } + + // TODO(ianh): in theory, [selection] could become null between when + // we last called describeSemanticsConfiguration and when the + // callbacks are invoked, in which case the callbacks will crash... + + void _handleSetSelection(TextSelection selection) { + _setSelection(selection, SelectionChangedCause.keyboard); + } + + void _handleMoveCursorForwardByCharacter(bool extendSelection) { + assert(selection != null); + final int? extentOffset = + _textPainter.getOffsetAfter(selection!.extentOffset); + if (extentOffset == null) { + return; + } + final int baseOffset = + !extendSelection ? extentOffset : selection!.baseOffset; + _setSelection( + TextSelection(baseOffset: baseOffset, extentOffset: extentOffset), + SelectionChangedCause.keyboard, + ); + } + + void _handleMoveCursorBackwardByCharacter(bool extendSelection) { + assert(selection != null); + final int? extentOffset = + _textPainter.getOffsetBefore(selection!.extentOffset); + if (extentOffset == null) { + return; + } + final int baseOffset = + !extendSelection ? extentOffset : selection!.baseOffset; + _setSelection( + TextSelection(baseOffset: baseOffset, extentOffset: extentOffset), + SelectionChangedCause.keyboard, + ); + } + + void _handleMoveCursorForwardByWord(bool extendSelection) { + assert(selection != null); + final TextRange currentWord = + _textPainter.getWordBoundary(selection!.extent); + final TextRange? nextWord = _getNextWord(currentWord.end); + if (nextWord == null) { + return; + } + final int baseOffset = + extendSelection ? selection!.baseOffset : nextWord.start; + _setSelection( + TextSelection( + baseOffset: baseOffset, + extentOffset: nextWord.start, + ), + SelectionChangedCause.keyboard, + ); + } + + void _handleMoveCursorBackwardByWord(bool extendSelection) { + assert(selection != null); + final TextRange currentWord = + _textPainter.getWordBoundary(selection!.extent); + final TextRange? previousWord = _getPreviousWord(currentWord.start - 1); + if (previousWord == null) { + return; + } + final int baseOffset = + extendSelection ? selection!.baseOffset : previousWord.start; + _setSelection( + TextSelection( + baseOffset: baseOffset, + extentOffset: previousWord.start, + ), + SelectionChangedCause.keyboard, + ); + } + + TextRange? _getNextWord(int offset) { + while (true) { + final TextRange range = + _textPainter.getWordBoundary(TextPosition(offset: offset)); + if (!range.isValid || range.isCollapsed) { + return null; + } + if (!_onlyWhitespace(range)) { + return range; + } + offset = range.end; + } + } + + TextRange? _getPreviousWord(int offset) { + while (offset >= 0) { + final TextRange range = + _textPainter.getWordBoundary(TextPosition(offset: offset)); + if (!range.isValid || range.isCollapsed) { + return null; + } + if (!_onlyWhitespace(range)) { + return range; + } + offset = range.start - 1; + } + return null; + } + + // Check if the given text range only contains white space or separator + // characters. + // + // Includes newline characters from ASCII and separators from the + // [unicode separator category](https://www.compart.com/en/unicode/category/Zs) + // TODO(zanderso): replace when we expose this ICU information. + bool _onlyWhitespace(TextRange range) { + for (int i = range.start; i < range.end; i++) { + final int codeUnit = text!.codeUnitAt(i)!; + if (!TextLayoutMetrics.isWhitespace(codeUnit)) { + return false; + } + } + return true; + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _foregroundRenderObject?.attach(owner); + _backgroundRenderObject?.attach(owner); + + _tap = TapGestureRecognizer(debugOwner: this) + ..onTapDown = _handleTapDown + ..onTap = _handleTap; + _longPress = LongPressGestureRecognizer(debugOwner: this) + ..onLongPress = _handleLongPress; + _offset.addListener(markNeedsPaint); + _showHideCursor(); + _showCursor.addListener(_showHideCursor); + } + + @override + void detach() { + _tap.dispose(); + _longPress.dispose(); + _offset.removeListener(markNeedsPaint); + _showCursor.removeListener(_showHideCursor); + super.detach(); + _foregroundRenderObject?.detach(); + _backgroundRenderObject?.detach(); + } + + @override + void redepthChildren() { + final RenderObject? foregroundChild = _foregroundRenderObject; + final RenderObject? backgroundChild = _backgroundRenderObject; + if (foregroundChild != null) { + redepthChild(foregroundChild); + } + if (backgroundChild != null) { + redepthChild(backgroundChild); + } + super.redepthChildren(); + } + + @override + void visitChildren(RenderObjectVisitor visitor) { + final RenderObject? foregroundChild = _foregroundRenderObject; + final RenderObject? backgroundChild = _backgroundRenderObject; + if (foregroundChild != null) { + visitor(foregroundChild); + } + if (backgroundChild != null) { + visitor(backgroundChild); + } + super.visitChildren(visitor); + } + + bool get _isMultiline => maxLines != 1; + + Axis get _viewportAxis => _isMultiline ? Axis.vertical : Axis.horizontal; + + Offset get _paintOffset { + return switch (_viewportAxis) { + Axis.horizontal => Offset(-offset.pixels, 0.0), + Axis.vertical => Offset(0.0, -offset.pixels), + }; + } + + double get _viewportExtent { + assert(hasSize); + return switch (_viewportAxis) { + Axis.horizontal => size.width, + Axis.vertical => size.height, + }; + } + + double _getMaxScrollExtent(Size contentSize) { + assert(hasSize); + return switch (_viewportAxis) { + Axis.horizontal => math.max(0.0, contentSize.width - size.width), + Axis.vertical => math.max(0.0, contentSize.height - size.height), + }; + } + + // We need to check the paint offset here because during animation, the start of + // the text may position outside the visible region even when the text fits. + bool get _hasVisualOverflow => + _maxScrollExtent > 0 || _paintOffset != Offset.zero; + + /// Returns the local coordinates of the endpoints of the given selection. + /// + /// If the selection is collapsed (and therefore occupies a single point), the + /// returned list is of length one. Otherwise, the selection is not collapsed + /// and the returned list is of length two. In this case, however, the two + /// points might actually be co-located (e.g., because of a bidirectional + /// selection that contains some text but whose ends meet in the middle). + /// + /// See also: + /// + /// * [getLocalRectForCaret], which is the equivalent but for + /// a [TextPosition] rather than a [TextSelection]. + List getEndpointsForSelection(TextSelection selection) { + _computeTextMetricsIfNeeded(); + + final Offset paintOffset = _paintOffset; + + final List boxes = selection.isCollapsed + ? [] + : _textPainter.getBoxesForSelection(selection, + boxHeightStyle: selectionHeightStyle, + boxWidthStyle: selectionWidthStyle); + if (boxes.isEmpty) { + // TODO(mpcomplete): This doesn't work well at an RTL/LTR boundary. + final Offset caretOffset = + _textPainter.getOffsetForCaret(selection.extent, _caretPrototype); + final Offset start = + Offset(0.0, preferredLineHeight) + caretOffset + paintOffset; + return [TextSelectionPoint(start, null)]; + } else { + final Offset start = Offset( + clampDouble(boxes.first.start, 0, _textPainter.size.width), + boxes.first.bottom) + + paintOffset; + final Offset end = Offset( + clampDouble(boxes.last.end, 0, _textPainter.size.width), + boxes.last.bottom) + + paintOffset; + return [ + TextSelectionPoint(start, boxes.first.direction), + TextSelectionPoint(end, boxes.last.direction), + ]; + } + } + + /// Returns the smallest [Rect], in the local coordinate system, that covers + /// the text within the [TextRange] specified. + /// + /// This method is used to calculate the approximate position of the IME bar + /// on iOS. + /// + /// Returns null if [TextRange.isValid] is false for the given `range`, or the + /// given `range` is collapsed. + Rect? getRectForComposingRange(TextRange range) { + if (!range.isValid || range.isCollapsed) { + return null; + } + _computeTextMetricsIfNeeded(); + + final List boxes = _textPainter.getBoxesForSelection( + TextSelection(baseOffset: range.start, extentOffset: range.end), + boxHeightStyle: selectionHeightStyle, + boxWidthStyle: selectionWidthStyle, + ); + + return boxes + .fold( + null, + (Rect? accum, TextBox incoming) => + accum?.expandToInclude(incoming.toRect()) ?? incoming.toRect(), + ) + ?.shift(_paintOffset); + } + + /// Returns the position in the text for the given global coordinate. + /// + /// See also: + /// + /// * [getLocalRectForCaret], which is the reverse operation, taking + /// a [TextPosition] and returning a [Rect]. + /// * [TextPainter.getPositionForOffset], which is the equivalent method + /// for a [TextPainter] object. + TextPosition getPositionForPoint(Offset globalPosition) { + _computeTextMetricsIfNeeded(); + return _textPainter + .getPositionForOffset(globalToLocal(globalPosition) - _paintOffset); + } + + /// Returns the [Rect] in local coordinates for the caret at the given text + /// position. + /// + /// See also: + /// + /// * [getPositionForPoint], which is the reverse operation, taking + /// an [Offset] in global coordinates and returning a [TextPosition]. + /// * [getEndpointsForSelection], which is the equivalent but for + /// a selection rather than a particular text position. + /// * [TextPainter.getOffsetForCaret], the equivalent method for a + /// [TextPainter] object. + Rect getLocalRectForCaret(TextPosition caretPosition) { + _computeTextMetricsIfNeeded(); + final Rect caretPrototype = _caretPrototype; + final Offset caretOffset = + _textPainter.getOffsetForCaret(caretPosition, caretPrototype); + Rect caretRect = caretPrototype.shift(caretOffset + cursorOffset); + final double scrollableWidth = + math.max(_textPainter.width + _caretMargin, size.width); + + final double caretX = clampDouble( + caretRect.left, 0, math.max(scrollableWidth - _caretMargin, 0)); + caretRect = Offset(caretX, caretRect.top) & caretRect.size; + + final double fullHeight = + _textPainter.getFullHeightForCaret(caretPosition, caretPrototype); + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + // Center the caret vertically along the text. + final double heightDiff = fullHeight - caretRect.height; + caretRect = Rect.fromLTWH( + caretRect.left, + caretRect.top + heightDiff / 2, + caretRect.width, + caretRect.height, + ); + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + // Override the height to take the full height of the glyph at the TextPosition + // when not on iOS. iOS has special handling that creates a taller caret. + // TODO(garyq): see https://github.com/flutter/flutter/issues/120836. + final double caretHeight = cursorHeight; + // Center the caret vertically along the text. + final double heightDiff = fullHeight - caretHeight; + caretRect = Rect.fromLTWH( + caretRect.left, + caretRect.top - _kCaretHeightOffset + heightDiff / 2, + caretRect.width, + caretHeight, + ); + } + + caretRect = caretRect.shift(_paintOffset); + return caretRect.shift(_snapToPhysicalPixel(caretRect.topLeft)); + } + + @override + double computeMinIntrinsicWidth(double height) { + final List placeholderDimensions = + layoutInlineChildren( + double.infinity, + (RenderBox child, BoxConstraints constraints) => + Size(child.getMinIntrinsicWidth(double.infinity), 0.0), + ChildLayoutHelper.getDryBaseline, + ); + final (double minWidth, double maxWidth) = _adjustConstraints(); + return (_textIntrinsics + ..setPlaceholderDimensions(placeholderDimensions) + ..layout(minWidth: minWidth, maxWidth: maxWidth)) + .minIntrinsicWidth; + } + + @override + double computeMaxIntrinsicWidth(double height) { + final List placeholderDimensions = + layoutInlineChildren( + double.infinity, + // Height and baseline is irrelevant as all text will be laid + // out in a single line. Therefore, using 0.0 as a dummy for the height. + (RenderBox child, BoxConstraints constraints) => + Size(child.getMaxIntrinsicWidth(double.infinity), 0.0), + ChildLayoutHelper.getDryBaseline, + ); + final (double minWidth, double maxWidth) = _adjustConstraints(); + return (_textIntrinsics + ..setPlaceholderDimensions(placeholderDimensions) + ..layout(minWidth: minWidth, maxWidth: maxWidth)) + .maxIntrinsicWidth + + _caretMargin; + } + + /// An estimate of the height of a line in the text. See [TextPainter.preferredLineHeight]. + /// This does not require the layout to be updated. + double get preferredLineHeight => _textPainter.preferredLineHeight; + + int? _cachedLineBreakCount; + int _countHardLineBreaks(String text) { + final int? cachedValue = _cachedLineBreakCount; + if (cachedValue != null) { + return cachedValue; + } + int count = 0; + for (int index = 0; index < text.length; index += 1) { + switch (text.codeUnitAt(index)) { + case 0x000A: // LF + case 0x0085: // NEL + case 0x000B: // VT + case 0x000C: // FF, treating it as a regular line separator + case 0x2028: // LS + case 0x2029: // PS + count += 1; + } + } + return _cachedLineBreakCount = count; + } + + double _preferredHeight(double width) { + final int? maxLines = this.maxLines; + final int? minLines = this.minLines ?? maxLines; + final double minHeight = preferredLineHeight * (minLines ?? 0); + assert(maxLines != 1 || _textIntrinsics.maxLines == 1); + + if (maxLines == null) { + final double estimatedHeight; + if (width == double.infinity) { + estimatedHeight = + preferredLineHeight * (_countHardLineBreaks(plainText) + 1); + } else { + final (double minWidth, double maxWidth) = + _adjustConstraints(maxWidth: width); + estimatedHeight = (_textIntrinsics + ..layout(minWidth: minWidth, maxWidth: maxWidth)) + .height; + } + return math.max(estimatedHeight, minHeight); + } + + // Special case maxLines == 1 since it forces the scrollable direction + // to be horizontal. Report the real height to prevent the text from being + // clipped. + if (maxLines == 1) { + // The _layoutText call lays out the paragraph using infinite width when + // maxLines == 1. Also _textPainter.maxLines will be set to 1 so should + // there be any line breaks only the first line is shown. + final (double minWidth, double maxWidth) = + _adjustConstraints(maxWidth: width); + return (_textIntrinsics..layout(minWidth: minWidth, maxWidth: maxWidth)) + .height; + } + if (minLines == maxLines) { + return minHeight; + } + final double maxHeight = preferredLineHeight * maxLines; + final (double minWidth, double maxWidth) = + _adjustConstraints(maxWidth: width); + return clampDouble( + (_textIntrinsics..layout(minWidth: minWidth, maxWidth: maxWidth)).height, + minHeight, + maxHeight, + ); + } + + @override + double computeMinIntrinsicHeight(double width) => + getMaxIntrinsicHeight(width); + + @override + double computeMaxIntrinsicHeight(double width) { + _textIntrinsics.setPlaceholderDimensions( + layoutInlineChildren(width, ChildLayoutHelper.dryLayoutChild, + ChildLayoutHelper.getDryBaseline), + ); + return _preferredHeight(width); + } + + @override + double computeDistanceToActualBaseline(TextBaseline baseline) { + _computeTextMetricsIfNeeded(); + return _textPainter.computeDistanceToActualBaseline(baseline); + } + + @override + bool hitTestSelf(Offset position) => true; + + @override + @protected + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + final Offset effectivePosition = position - _paintOffset; + final GlyphInfo? glyph = + _textPainter.getClosestGlyphForOffset(effectivePosition); + // The hit-test can't fall through the horizontal gaps between visually + // adjacent characters on the same line, even with a large letter-spacing or + // text justification, as graphemeClusterLayoutBounds.width is the advance + // width to the next character, so there's no gap between their + // graphemeClusterLayoutBounds rects. + final InlineSpan? spanHit = glyph != null && + glyph.graphemeClusterLayoutBounds.contains(effectivePosition) + ? _textPainter.text!.getSpanForPosition( + TextPosition(offset: glyph.graphemeClusterCodeUnitRange.start)) + : null; + switch (spanHit) { + case final HitTestTarget span: + result.add(HitTestEntry(span)); + return true; + case _: + return hitTestInlineChildren(result, effectivePosition); + } + } + + late TapGestureRecognizer _tap; + late LongPressGestureRecognizer _longPress; + + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + assert(debugHandleEvent(event, entry)); + if (event is PointerDownEvent) { + assert(!debugNeedsLayout); + + if (!ignorePointer) { + // Propagates the pointer event to selection handlers. + _tap.addPointer(event); + _longPress.addPointer(event); + } + } + } + + Offset? _lastTapDownPosition; + Offset? _lastSecondaryTapDownPosition; + + /// {@template flutter.rendering.RenderEditable.lastSecondaryTapDownPosition} + /// The position of the most recent secondary tap down event on this text + /// input. + /// {@endtemplate} + Offset? get lastSecondaryTapDownPosition => _lastSecondaryTapDownPosition; + + /// Tracks the position of a secondary tap event. + /// + /// Should be called before attempting to change the selection based on the + /// position of a secondary tap. + void handleSecondaryTapDown(TapDownDetails details) { + _lastTapDownPosition = details.globalPosition; + _lastSecondaryTapDownPosition = details.globalPosition; + } + + /// If [ignorePointer] is false (the default) then this method is called by + /// the internal gesture recognizer's [TapGestureRecognizer.onTapDown] + /// callback. + /// + /// When [ignorePointer] is true, an ancestor widget must respond to tap + /// down events by calling this method. + void handleTapDown(TapDownDetails details) { + _lastTapDownPosition = details.globalPosition; + } + + void _handleTapDown(TapDownDetails details) { + assert(!ignorePointer); + handleTapDown(details); + } + + /// If [ignorePointer] is false (the default) then this method is called by + /// the internal gesture recognizer's [TapGestureRecognizer.onTap] + /// callback. + /// + /// When [ignorePointer] is true, an ancestor widget must respond to tap + /// events by calling this method. + void handleTap() { + selectPosition(cause: SelectionChangedCause.tap); + } + + void _handleTap() { + assert(!ignorePointer); + handleTap(); + } + + /// If [ignorePointer] is false (the default) then this method is called by + /// the internal gesture recognizer's [DoubleTapGestureRecognizer.onDoubleTap] + /// callback. + /// + /// When [ignorePointer] is true, an ancestor widget must respond to double + /// tap events by calling this method. + void handleDoubleTap() { + selectWord(cause: SelectionChangedCause.doubleTap); + } + + /// If [ignorePointer] is false (the default) then this method is called by + /// the internal gesture recognizer's [LongPressGestureRecognizer.onLongPress] + /// callback. + /// + /// When [ignorePointer] is true, an ancestor widget must respond to long + /// press events by calling this method. + void handleLongPress() { + selectWord(cause: SelectionChangedCause.longPress); + } + + void _handleLongPress() { + assert(!ignorePointer); + handleLongPress(); + } + + /// Move selection to the location of the last tap down. + /// + /// {@template flutter.rendering.RenderEditable.selectPosition} + /// This method is mainly used to translate user inputs in global positions + /// into a [TextSelection]. When used in conjunction with a [EditableText], + /// the selection change is fed back into [TextEditingController.selection]. + /// + /// If you have a [TextEditingController], it's generally easier to + /// programmatically manipulate its `value` or `selection` directly. + /// {@endtemplate} + void selectPosition({required SelectionChangedCause cause}) { + selectPositionAt(from: _lastTapDownPosition!, cause: cause); + } + + /// Select text between the global positions [from] and [to]. + /// + /// [from] corresponds to the [TextSelection.baseOffset], and [to] corresponds + /// to the [TextSelection.extentOffset]. + void selectPositionAt( + {required Offset from, + Offset? to, + required SelectionChangedCause cause}) { + _computeTextMetricsIfNeeded(); + final TextPosition fromPosition = + _textPainter.getPositionForOffset(globalToLocal(from) - _paintOffset); + final TextPosition? toPosition = to == null + ? null + : _textPainter.getPositionForOffset(globalToLocal(to) - _paintOffset); + + final int baseOffset = fromPosition.offset; + final int extentOffset = toPosition?.offset ?? fromPosition.offset; + + final TextSelection newSelection = TextSelection( + baseOffset: baseOffset, + extentOffset: extentOffset, + affinity: fromPosition.affinity, + ); + + _setSelection(newSelection, cause); + } + + /// {@macro flutter.painting.TextPainter.wordBoundaries} + WordBoundary get wordBoundaries => _textPainter.wordBoundaries; + + /// Select a word around the location of the last tap down. + /// + /// {@macro flutter.rendering.RenderEditable.selectPosition} + void selectWord({required SelectionChangedCause cause}) { + selectWordsInRange(from: _lastTapDownPosition!, cause: cause); + } + + /// Selects the set words of a paragraph that intersect a given range of global positions. + /// + /// The set of words selected are not strictly bounded by the range of global positions. + /// + /// The first and last endpoints of the selection will always be at the + /// beginning and end of a word respectively. + /// + /// {@macro flutter.rendering.RenderEditable.selectPosition} + void selectWordsInRange( + {required Offset from, + Offset? to, + required SelectionChangedCause cause}) { + _computeTextMetricsIfNeeded(); + final TextPosition fromPosition = + _textPainter.getPositionForOffset(globalToLocal(from) - _paintOffset); + final TextSelection fromWord = getWordAtOffset(fromPosition); + final TextPosition toPosition = to == null + ? fromPosition + : _textPainter.getPositionForOffset(globalToLocal(to) - _paintOffset); + final TextSelection toWord = + toPosition == fromPosition ? fromWord : getWordAtOffset(toPosition); + final bool isFromWordBeforeToWord = fromWord.start < toWord.end; + + _setSelection( + TextSelection( + baseOffset: isFromWordBeforeToWord + ? fromWord.base.offset + : fromWord.extent.offset, + extentOffset: + isFromWordBeforeToWord ? toWord.extent.offset : toWord.base.offset, + affinity: fromWord.affinity, + ), + cause, + ); + } + + /// Move the selection to the beginning or end of a word. + /// + /// {@macro flutter.rendering.RenderEditable.selectPosition} + void selectWordEdge({required SelectionChangedCause cause}) { + _computeTextMetricsIfNeeded(); + assert(_lastTapDownPosition != null); + final TextPosition position = _textPainter.getPositionForOffset( + globalToLocal(_lastTapDownPosition!) - _paintOffset); + final TextRange word = _textPainter.getWordBoundary(position); + late TextSelection newSelection; + if (position.offset <= word.start) { + newSelection = TextSelection.collapsed(offset: word.start); + } else { + newSelection = TextSelection.collapsed( + offset: word.end, affinity: TextAffinity.upstream); + } + _setSelection(newSelection, cause); + } + + /// Returns a [TextSelection] that encompasses the word at the given + /// [TextPosition]. + @visibleForTesting + TextSelection getWordAtOffset(TextPosition position) { + // When long-pressing past the end of the text, we want a collapsed cursor. + if (position.offset >= plainText.length) { + return TextSelection.fromPosition(TextPosition( + offset: plainText.length, affinity: TextAffinity.upstream)); + } + // If text is obscured, the entire sentence should be treated as one word. + if (obscureText) { + return TextSelection(baseOffset: 0, extentOffset: plainText.length); + } + final TextRange word = _textPainter.getWordBoundary(position); + final int effectiveOffset; + switch (position.affinity) { + case TextAffinity.upstream: + // upstream affinity is effectively -1 in text position. + effectiveOffset = position.offset - 1; + case TextAffinity.downstream: + effectiveOffset = position.offset; + } + assert(effectiveOffset >= 0); + + // On iOS, select the previous word if there is a previous word, or select + // to the end of the next word if there is a next word. Select nothing if + // there is neither a previous word nor a next word. + // + // If the platform is Android and the text is read only, try to select the + // previous word if there is one; otherwise, select the single whitespace at + // the position. + if (effectiveOffset > 0 && + TextLayoutMetrics.isWhitespace(plainText.codeUnitAt(effectiveOffset))) { + final TextRange? previousWord = _getPreviousWord(word.start); + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + if (previousWord == null) { + final TextRange? nextWord = _getNextWord(word.start); + if (nextWord == null) { + return TextSelection.collapsed(offset: position.offset); + } + return TextSelection( + baseOffset: position.offset, + extentOffset: nextWord.end, + ); + } + return TextSelection( + baseOffset: previousWord.start, + extentOffset: position.offset, + ); + case TargetPlatform.android: + if (readOnly) { + if (previousWord == null) { + return TextSelection( + baseOffset: position.offset, + extentOffset: position.offset + 1, + ); + } + return TextSelection( + baseOffset: previousWord.start, + extentOffset: position.offset, + ); + } + case TargetPlatform.fuchsia: + case TargetPlatform.macOS: + case TargetPlatform.linux: + case TargetPlatform.windows: + break; + } + } + + return TextSelection(baseOffset: word.start, extentOffset: word.end); + } + + // Placeholder dimensions representing the sizes of child inline widgets. + // + // These need to be cached because the text painter's placeholder dimensions + // will be overwritten during intrinsic width/height calculations and must be + // restored to the original values before final layout and painting. + List? _placeholderDimensions; + + (double minWidth, double maxWidth) _adjustConstraints( + {double minWidth = 0.0, double maxWidth = double.infinity}) { + final double availableMaxWidth = math.max(0.0, maxWidth - _caretMargin); + final double availableMinWidth = math.min(minWidth, availableMaxWidth); + return ( + forceLine ? availableMaxWidth : availableMinWidth, + _isMultiline ? availableMaxWidth : double.infinity, + ); + } + + // Computes the text metrics if `_textPainter`'s layout information was marked + // as dirty. + // + // This method must be called in `RenderEditable`'s public methods that expose + // `_textPainter`'s metrics. For instance, `systemFontsDidChange` sets + // _textPainter._paragraph to null, so accessing _textPainter's metrics + // immediately after `systemFontsDidChange` without first calling this method + // may crash. + // + // This method is also called in various paint methods (`RenderEditable.paint` + // as well as its foreground/background painters' `paint`). It's needed + // because invisible render objects kept in the tree by `KeepAlive` may not + // get a chance to do layout but can still paint. + // See https://github.com/flutter/flutter/issues/84896. + // + // This method only re-computes layout if the underlying `_textPainter`'s + // layout cache is invalidated (by calling `TextPainter.markNeedsLayout`), or + // the constraints used to layout the `_textPainter` is different. See + // `TextPainter.layout`. + void _computeTextMetricsIfNeeded() { + final (double minWidth, double maxWidth) = _adjustConstraints( + minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); + _textPainter.layout(minWidth: minWidth, maxWidth: maxWidth); + } + + late Rect _caretPrototype; + + // TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/120836 + // + /// On iOS, the cursor is taller than the cursor on Android. The height + /// of the cursor for iOS is approximate and obtained through an eyeball + /// comparison. + void _computeCaretPrototype() { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + _caretPrototype = + Rect.fromLTWH(0.0, 0.0, cursorWidth, cursorHeight + 2); + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + _caretPrototype = Rect.fromLTWH(0.0, _kCaretHeightOffset, cursorWidth, + cursorHeight - 2.0 * _kCaretHeightOffset); + } + } + + // Computes the offset to apply to the given [sourceOffset] so it perfectly + // snaps to physical pixels. + Offset _snapToPhysicalPixel(Offset sourceOffset) { + final Offset globalOffset = localToGlobal(sourceOffset); + final double pixelMultiple = 1.0 / _devicePixelRatio; + return Offset( + globalOffset.dx.isFinite + ? (globalOffset.dx / pixelMultiple).round() * pixelMultiple - + globalOffset.dx + : 0, + globalOffset.dy.isFinite + ? (globalOffset.dy / pixelMultiple).round() * pixelMultiple - + globalOffset.dy + : 0, + ); + } + + @override + @protected + Size computeDryLayout(covariant BoxConstraints constraints) { + final (double minWidth, double maxWidth) = _adjustConstraints( + minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); + _textIntrinsics + ..setPlaceholderDimensions(layoutInlineChildren(constraints.maxWidth, + ChildLayoutHelper.dryLayoutChild, ChildLayoutHelper.getDryBaseline)) + ..layout(minWidth: minWidth, maxWidth: maxWidth); + final double width = forceLine + ? constraints.maxWidth + : constraints.constrainWidth(_textIntrinsics.size.width + _caretMargin); + return Size(width, + constraints.constrainHeight(_preferredHeight(constraints.maxWidth))); + } + + @override + double computeDryBaseline( + covariant BoxConstraints constraints, TextBaseline baseline) { + final (double minWidth, double maxWidth) = _adjustConstraints( + minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); + _textIntrinsics + ..setPlaceholderDimensions(layoutInlineChildren(constraints.maxWidth, + ChildLayoutHelper.dryLayoutChild, ChildLayoutHelper.getDryBaseline)) + ..layout(minWidth: minWidth, maxWidth: maxWidth); + return _textIntrinsics.computeDistanceToActualBaseline(baseline); + } + + @override + void performLayout() { + final BoxConstraints constraints = this.constraints; + _placeholderDimensions = layoutInlineChildren(constraints.maxWidth, + ChildLayoutHelper.layoutChild, ChildLayoutHelper.getBaseline); + final (double minWidth, double maxWidth) = _adjustConstraints( + minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); + _textPainter + ..setPlaceholderDimensions(_placeholderDimensions) + ..layout(minWidth: minWidth, maxWidth: maxWidth); + positionInlineChildren(_textPainter.inlinePlaceholderBoxes!); + _computeCaretPrototype(); + + final double width = forceLine + ? constraints.maxWidth + : constraints.constrainWidth(_textPainter.width + _caretMargin); + assert(maxLines != 1 || _textPainter.maxLines == 1); + final double preferredHeight = switch (maxLines) { + null => + math.max(_textPainter.height, preferredLineHeight * (minLines ?? 0)), + 1 => _textPainter.height, + final int maxLines => clampDouble( + _textPainter.height, + preferredLineHeight * (minLines ?? maxLines), + preferredLineHeight * maxLines, + ), + }; + + size = Size(width, constraints.constrainHeight(preferredHeight)); + final Size contentSize = + Size(_textPainter.width + _caretMargin, _textPainter.height); + + final BoxConstraints painterConstraints = BoxConstraints.tight(contentSize); + + _foregroundRenderObject?.layout(painterConstraints); + _backgroundRenderObject?.layout(painterConstraints); + + _maxScrollExtent = _getMaxScrollExtent(contentSize); + offset.applyViewportDimension(_viewportExtent); + offset.applyContentDimensions(0.0, _maxScrollExtent); + } + + // The relative origin in relation to the distance the user has theoretically + // dragged the floating cursor offscreen. This value is used to account for the + // difference in the rendering position and the raw offset value. + Offset _relativeOrigin = Offset.zero; + Offset? _previousOffset; + bool _shouldResetOrigin = true; + bool _resetOriginOnLeft = false; + bool _resetOriginOnRight = false; + bool _resetOriginOnTop = false; + bool _resetOriginOnBottom = false; + double? _resetFloatingCursorAnimationValue; + + static Offset _calculateAdjustedCursorOffset( + Offset offset, Rect boundingRects) { + final double adjustedX = + clampDouble(offset.dx, boundingRects.left, boundingRects.right); + final double adjustedY = + clampDouble(offset.dy, boundingRects.top, boundingRects.bottom); + return Offset(adjustedX, adjustedY); + } + + /// Returns the position within the text field closest to the raw cursor offset. + /// + /// See also: + /// + /// * [FloatingCursorDragState], which explains the floating cursor feature + /// in detail. + Offset calculateBoundedFloatingCursorOffset(Offset rawCursorOffset, + {bool? shouldResetOrigin}) { + Offset deltaPosition = Offset.zero; + final double topBound = -floatingCursorAddedMargin.top; + final double bottomBound = math.min(size.height, _textPainter.height) - + preferredLineHeight + + floatingCursorAddedMargin.bottom; + final double leftBound = -floatingCursorAddedMargin.left; + final double rightBound = math.min(size.width, _textPainter.width) + + floatingCursorAddedMargin.right; + final Rect boundingRects = + Rect.fromLTRB(leftBound, topBound, rightBound, bottomBound); + + if (shouldResetOrigin != null) { + _shouldResetOrigin = shouldResetOrigin; + } + + if (!_shouldResetOrigin) { + return _calculateAdjustedCursorOffset(rawCursorOffset, boundingRects); + } + + if (_previousOffset != null) { + deltaPosition = rawCursorOffset - _previousOffset!; + } + + // If the raw cursor offset has gone off an edge, we want to reset the relative + // origin of the dragging when the user drags back into the field. + if (_resetOriginOnLeft && deltaPosition.dx > 0) { + _relativeOrigin = + Offset(rawCursorOffset.dx - boundingRects.left, _relativeOrigin.dy); + _resetOriginOnLeft = false; + } else if (_resetOriginOnRight && deltaPosition.dx < 0) { + _relativeOrigin = + Offset(rawCursorOffset.dx - boundingRects.right, _relativeOrigin.dy); + _resetOriginOnRight = false; + } + if (_resetOriginOnTop && deltaPosition.dy > 0) { + _relativeOrigin = + Offset(_relativeOrigin.dx, rawCursorOffset.dy - boundingRects.top); + _resetOriginOnTop = false; + } else if (_resetOriginOnBottom && deltaPosition.dy < 0) { + _relativeOrigin = + Offset(_relativeOrigin.dx, rawCursorOffset.dy - boundingRects.bottom); + _resetOriginOnBottom = false; + } + + final double currentX = rawCursorOffset.dx - _relativeOrigin.dx; + final double currentY = rawCursorOffset.dy - _relativeOrigin.dy; + final Offset adjustedOffset = _calculateAdjustedCursorOffset( + Offset(currentX, currentY), boundingRects); + + if (currentX < boundingRects.left && deltaPosition.dx < 0) { + _resetOriginOnLeft = true; + } else if (currentX > boundingRects.right && deltaPosition.dx > 0) { + _resetOriginOnRight = true; + } + if (currentY < boundingRects.top && deltaPosition.dy < 0) { + _resetOriginOnTop = true; + } else if (currentY > boundingRects.bottom && deltaPosition.dy > 0) { + _resetOriginOnBottom = true; + } + + _previousOffset = rawCursorOffset; + + return adjustedOffset; + } + + /// Sets the screen position of the floating cursor and the text position + /// closest to the cursor. + /// + /// See also: + /// + /// * [FloatingCursorDragState], which explains the floating cursor feature + /// in detail. + void setFloatingCursor(FloatingCursorDragState state, Offset boundedOffset, + TextPosition lastTextPosition, + {double? resetLerpValue}) { + if (state == FloatingCursorDragState.End) { + _relativeOrigin = Offset.zero; + _previousOffset = null; + _shouldResetOrigin = true; + _resetOriginOnBottom = false; + _resetOriginOnTop = false; + _resetOriginOnRight = false; + _resetOriginOnBottom = false; + } + _floatingCursorOn = state != FloatingCursorDragState.End; + _resetFloatingCursorAnimationValue = resetLerpValue; + if (_floatingCursorOn) { + _floatingCursorTextPosition = lastTextPosition; + final double? animationValue = _resetFloatingCursorAnimationValue; + final EdgeInsets sizeAdjustment = animationValue != null + ? EdgeInsets.lerp( + _kFloatingCursorSizeIncrease, EdgeInsets.zero, animationValue)! + : _kFloatingCursorSizeIncrease; + _caretPainter.floatingCursorRect = + sizeAdjustment.inflateRect(_caretPrototype).shift(boundedOffset); + } else { + _caretPainter.floatingCursorRect = null; + } + _caretPainter.showRegularCaret = _resetFloatingCursorAnimationValue == null; + } + + MapEntry _lineNumberFor( + TextPosition startPosition, List metrics) { + // TODO(LongCatIsLooong): include line boundaries information in + // ui.LineMetrics, then we can get rid of this. + final Offset offset = + _textPainter.getOffsetForCaret(startPosition, Rect.zero); + for (final ui.LineMetrics lineMetrics in metrics) { + if (lineMetrics.baseline > offset.dy) { + return MapEntry( + lineMetrics.lineNumber, Offset(offset.dx, lineMetrics.baseline)); + } + } + assert(startPosition.offset == 0, + 'unable to find the line for $startPosition'); + return MapEntry( + math.max(0, metrics.length - 1), + Offset( + offset.dx, + metrics.isNotEmpty + ? metrics.last.baseline + metrics.last.descent + : 0.0), + ); + } + + /// Starts a [VerticalCaretMovementRun] at the given location in the text, for + /// handling consecutive vertical caret movements. + /// + /// This can be used to handle consecutive upward/downward arrow key movements + /// in an input field. + /// + /// {@macro flutter.rendering.RenderEditable.verticalArrowKeyMovement} + /// + /// The [VerticalCaretMovementRun.isValid] property indicates whether the text + /// layout has changed and the vertical caret run is invalidated. + /// + /// The caller should typically discard a [VerticalCaretMovementRun] when + /// its [VerticalCaretMovementRun.isValid] becomes false, or on other + /// occasions where the vertical caret run should be interrupted. + VerticalCaretMovementRun startVerticalCaretMovement( + TextPosition startPosition) { + final List metrics = _textPainter.computeLineMetrics(); + final MapEntry currentLine = + _lineNumberFor(startPosition, metrics); + return VerticalCaretMovementRun._( + this, + metrics, + startPosition, + currentLine.key, + currentLine.value, + ); + } + + void _paintContents(PaintingContext context, Offset offset) { + final Offset effectiveOffset = offset + _paintOffset; + + if (selection != null && !_floatingCursorOn) { + _updateSelectionExtentsVisibility(effectiveOffset); + } + + final RenderBox? foregroundChild = _foregroundRenderObject; + final RenderBox? backgroundChild = _backgroundRenderObject; + + // The painters paint in the viewport's coordinate space, since the + // textPainter's coordinate space is not known to high level widgets. + if (backgroundChild != null) { + context.paintChild(backgroundChild, offset); + } + + _textPainter.paint(context.canvas, effectiveOffset); + paintInlineChildren(context, effectiveOffset); + + if (foregroundChild != null) { + context.paintChild(foregroundChild, offset); + } + } + + final LayerHandle _leaderLayerHandler = + LayerHandle(); + + void _paintHandleLayers(PaintingContext context, + List endpoints, Offset offset) { + Offset startPoint = endpoints[0].point; + startPoint = Offset( + clampDouble(startPoint.dx, 0.0, size.width), + clampDouble(startPoint.dy, 0.0, size.height), + ); + _leaderLayerHandler.layer = + LeaderLayer(link: startHandleLayerLink, offset: startPoint + offset); + context.pushLayer( + _leaderLayerHandler.layer!, + super.paint, + Offset.zero, + ); + if (endpoints.length == 2) { + Offset endPoint = endpoints[1].point; + endPoint = Offset( + clampDouble(endPoint.dx, 0.0, size.width), + clampDouble(endPoint.dy, 0.0, size.height), + ); + context.pushLayer( + LeaderLayer(link: endHandleLayerLink, offset: endPoint + offset), + super.paint, + Offset.zero, + ); + } + } + + @override + void applyPaintTransform(RenderBox child, Matrix4 transform) { + if (child == _foregroundRenderObject || child == _backgroundRenderObject) { + return; + } + defaultApplyPaintTransform(child, transform); + } + + @override + void paint(PaintingContext context, Offset offset) { + _computeTextMetricsIfNeeded(); + if (_hasVisualOverflow && clipBehavior != Clip.none) { + _clipRectLayer.layer = context.pushClipRect( + needsCompositing, + offset, + Offset.zero & size, + _paintContents, + clipBehavior: clipBehavior, + oldLayer: _clipRectLayer.layer, + ); + } else { + _clipRectLayer.layer = null; + _paintContents(context, offset); + } + final TextSelection? selection = this.selection; + if (selection != null && selection.isValid) { + _paintHandleLayers(context, getEndpointsForSelection(selection), offset); + } + } + + final LayerHandle _clipRectLayer = + LayerHandle(); + + @override + Rect? describeApproximatePaintClip(RenderObject child) { + switch (clipBehavior) { + case Clip.none: + return null; + case Clip.hardEdge: + case Clip.antiAlias: + case Clip.antiAliasWithSaveLayer: + return _hasVisualOverflow ? Offset.zero & size : null; + } + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(ColorProperty('cursorColor', cursorColor)); + properties.add( + DiagnosticsProperty>('showCursor', showCursor)); + properties.add(IntProperty('maxLines', maxLines)); + properties.add(IntProperty('minLines', minLines)); + properties.add( + DiagnosticsProperty('expands', expands, defaultValue: false)); + properties.add(ColorProperty('selectionColor', selectionColor)); + properties.add(DiagnosticsProperty('textScaler', textScaler, + defaultValue: TextScaler.noScaling)); + properties + .add(DiagnosticsProperty('locale', locale, defaultValue: null)); + properties.add(DiagnosticsProperty('selection', selection)); + properties.add(DiagnosticsProperty('offset', offset)); + } + + @override + List debugDescribeChildren() { + return [ + if (text != null) + text!.toDiagnosticsNode( + name: 'text', + style: DiagnosticsTreeStyle.transition, + ), + ]; + } +} + +class _RenderEditableCustomPaint extends RenderBox { + _RenderEditableCustomPaint({ + RenderEditablePainter? painter, + }) : _painter = painter, + super(); + + // zmtzawqlp + @override + _RenderEditable? get parent => super.parent as _RenderEditable?; + + @override + bool get isRepaintBoundary => true; + + @override + bool get sizedByParent => true; + + RenderEditablePainter? get painter => _painter; + RenderEditablePainter? _painter; + set painter(RenderEditablePainter? newValue) { + if (newValue == painter) { + return; + } + + final RenderEditablePainter? oldPainter = painter; + _painter = newValue; + + if (newValue?.shouldRepaint(oldPainter) ?? true) { + markNeedsPaint(); + } + + if (attached) { + oldPainter?.removeListener(markNeedsPaint); + newValue?.addListener(markNeedsPaint); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + // zmtzawlp + final _RenderEditable? parent = this.parent; + assert(parent != null); + final RenderEditablePainter? painter = this.painter; + if (painter != null && parent != null) { + parent._computeTextMetricsIfNeeded(); + painter.paint(context.canvas, size, parent); + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _painter?.addListener(markNeedsPaint); + } + + @override + void detach() { + _painter?.removeListener(markNeedsPaint); + super.detach(); + } + + @override + @protected + Size computeDryLayout(covariant BoxConstraints constraints) => + constraints.biggest; +} + +/// An interface that paints within a [RenderEditable]'s bounds, above or +/// beneath its text content. +/// +/// This painter is typically used for painting auxiliary content that depends +/// on text layout metrics (for instance, for painting carets and text highlight +/// blocks). It can paint independently from its [RenderEditable], allowing it +/// to repaint without triggering a repaint on the entire [RenderEditable] stack +/// when only auxiliary content changes (e.g. a blinking cursor) are present. It +/// will be scheduled to repaint when: +/// +/// * It's assigned to a new [RenderEditable] (replacing a prior +/// [RenderEditablePainter]) and the [shouldRepaint] method returns true. +/// * Any of the [RenderEditable]s it is attached to repaints. +/// * The [notifyListeners] method is called, which typically happens when the +/// painter's attributes change. +/// +/// See also: +/// +/// * [RenderEditable.foregroundPainter], which takes a [RenderEditablePainter] +/// and sets it as the foreground painter of the [RenderEditable]. +/// * [RenderEditable.painter], which takes a [RenderEditablePainter] +/// and sets it as the background painter of the [RenderEditable]. +/// * [CustomPainter], a similar class which paints within a [RenderCustomPaint]. +abstract class RenderEditablePainter extends ChangeNotifier { + /// Determines whether repaint is needed when a new [RenderEditablePainter] + /// is provided to a [RenderEditable]. + /// + /// If the new instance represents different information than the old + /// instance, then the method should return true, otherwise it should return + /// false. When [oldDelegate] is null, this method should always return true + /// unless the new painter initially does not paint anything. + /// + /// If the method returns false, then the [paint] call might be optimized + /// away. However, the [paint] method will get called whenever the + /// [RenderEditable]s it attaches to repaint, even if [shouldRepaint] returns + /// false. + bool shouldRepaint(RenderEditablePainter? oldDelegate); + + /// Paints within the bounds of a [RenderEditable]. + /// + /// The given [Canvas] has the same coordinate space as the [RenderEditable], + /// which may be different from the coordinate space the [RenderEditable]'s + /// [TextPainter] uses, when the text moves inside the [RenderEditable]. + /// + /// Paint operations performed outside of the region defined by the [canvas]'s + /// origin and the [size] parameter may get clipped, when [RenderEditable]'s + /// [RenderEditable.clipBehavior] is not [Clip.none]. + // zmtzawqlp + void paint(Canvas canvas, Size size, _RenderEditable renderEditable); +} + +class _TextHighlightPainter extends RenderEditablePainter { + _TextHighlightPainter({ + TextRange? highlightedRange, + Color? highlightColor, + }) : _highlightedRange = highlightedRange, + _highlightColor = highlightColor; + + final Paint highlightPaint = Paint(); + + Color? get highlightColor => _highlightColor; + Color? _highlightColor; + set highlightColor(Color? newValue) { + if (newValue == _highlightColor) { + return; + } + _highlightColor = newValue; + notifyListeners(); + } + + TextRange? get highlightedRange => _highlightedRange; + TextRange? _highlightedRange; + set highlightedRange(TextRange? newValue) { + if (newValue == _highlightedRange) { + return; + } + _highlightedRange = newValue; + notifyListeners(); + } + + /// Controls how tall the selection highlight boxes are computed to be. + /// + /// See [ui.BoxHeightStyle] for details on available styles. + ui.BoxHeightStyle get selectionHeightStyle => _selectionHeightStyle; + ui.BoxHeightStyle _selectionHeightStyle = ui.BoxHeightStyle.tight; + set selectionHeightStyle(ui.BoxHeightStyle value) { + if (_selectionHeightStyle == value) { + return; + } + _selectionHeightStyle = value; + notifyListeners(); + } + + /// Controls how wide the selection highlight boxes are computed to be. + /// + /// See [ui.BoxWidthStyle] for details on available styles. + ui.BoxWidthStyle get selectionWidthStyle => _selectionWidthStyle; + ui.BoxWidthStyle _selectionWidthStyle = ui.BoxWidthStyle.tight; + set selectionWidthStyle(ui.BoxWidthStyle value) { + if (_selectionWidthStyle == value) { + return; + } + _selectionWidthStyle = value; + notifyListeners(); + } + + // zmtzawqlp + @override + void paint(Canvas canvas, Size size, _RenderEditable renderEditable) { + final TextRange? range = highlightedRange; + final Color? color = highlightColor; + if (range == null || color == null || range.isCollapsed) { + return; + } + + highlightPaint.color = color; + // zmtzawqlp + final TextPainter textPainter = renderEditable._textPainter; + final List boxes = textPainter.getBoxesForSelection( + TextSelection(baseOffset: range.start, extentOffset: range.end), + boxHeightStyle: selectionHeightStyle, + boxWidthStyle: selectionWidthStyle, + ); + + for (final TextBox box in boxes) { + canvas.drawRect( + box.toRect().shift(renderEditable._paintOffset).intersect( + Rect.fromLTWH(0, 0, textPainter.width, textPainter.height)), + highlightPaint, + ); + } + } + + @override + bool shouldRepaint(RenderEditablePainter? oldDelegate) { + if (identical(oldDelegate, this)) { + return false; + } + if (oldDelegate == null) { + return highlightColor != null && highlightedRange != null; + } + return oldDelegate is! _TextHighlightPainter || + oldDelegate.highlightColor != highlightColor || + oldDelegate.highlightedRange != highlightedRange || + oldDelegate.selectionHeightStyle != selectionHeightStyle || + oldDelegate.selectionWidthStyle != selectionWidthStyle; + } +} + +class _CaretPainter extends RenderEditablePainter { + _CaretPainter(); + + bool get shouldPaint => _shouldPaint; + bool _shouldPaint = true; + set shouldPaint(bool value) { + if (shouldPaint == value) { + return; + } + _shouldPaint = value; + notifyListeners(); + } + + // This is directly manipulated by the RenderEditable during + // setFloatingCursor. + // + // When changing this value, the caller is responsible for ensuring that + // listeners are notified. + bool showRegularCaret = false; + + final Paint caretPaint = Paint(); + late final Paint floatingCursorPaint = Paint(); + + Color? get caretColor => _caretColor; + Color? _caretColor; + set caretColor(Color? value) { + if (caretColor?.value == value?.value) { + return; + } + + _caretColor = value; + notifyListeners(); + } + + Radius? get cursorRadius => _cursorRadius; + Radius? _cursorRadius; + set cursorRadius(Radius? value) { + if (_cursorRadius == value) { + return; + } + _cursorRadius = value; + notifyListeners(); + } + + Offset get cursorOffset => _cursorOffset; + Offset _cursorOffset = Offset.zero; + set cursorOffset(Offset value) { + if (_cursorOffset == value) { + return; + } + _cursorOffset = value; + notifyListeners(); + } + + Color? get backgroundCursorColor => _backgroundCursorColor; + Color? _backgroundCursorColor; + set backgroundCursorColor(Color? value) { + if (backgroundCursorColor?.value == value?.value) { + return; + } + + _backgroundCursorColor = value; + if (showRegularCaret) { + notifyListeners(); + } + } + + Rect? get floatingCursorRect => _floatingCursorRect; + Rect? _floatingCursorRect; + set floatingCursorRect(Rect? value) { + if (_floatingCursorRect == value) { + return; + } + _floatingCursorRect = value; + notifyListeners(); + } + + // zmtzawqlp + void paintRegularCursor(Canvas canvas, _RenderEditable renderEditable, + Color caretColor, TextPosition textPosition) { + final Rect integralRect = renderEditable.getLocalRectForCaret(textPosition); + if (shouldPaint) { + if (floatingCursorRect != null) { + final double distanceSquared = + (floatingCursorRect!.center - integralRect.center).distanceSquared; + if (distanceSquared < + _kShortestDistanceSquaredWithFloatingAndRegularCursors) { + return; + } + } + final Radius? radius = cursorRadius; + caretPaint.color = caretColor; + if (radius == null) { + canvas.drawRect(integralRect, caretPaint); + } else { + final RRect caretRRect = RRect.fromRectAndRadius(integralRect, radius); + canvas.drawRRect(caretRRect, caretPaint); + } + } + } + +// zmtzawqlp + @override + void paint(Canvas canvas, Size size, _RenderEditable renderEditable) { + // Compute the caret location even when `shouldPaint` is false. + + // final TextSelection? selection = renderEditable.selection; + // zmtzawqlp + final TextSelection? selection = + (renderEditable as ExtendedRenderEditable).getActualSelection(); + + if (selection == null || !selection.isCollapsed || !selection.isValid) { + return; + } + + final Rect? floatingCursorRect = this.floatingCursorRect; + + final Color? caretColor = floatingCursorRect == null + ? this.caretColor + : showRegularCaret + ? backgroundCursorColor + : null; + final TextPosition caretTextPosition = floatingCursorRect == null + ? selection.extent + : renderEditable._floatingCursorTextPosition; + + if (caretColor != null) { + paintRegularCursor(canvas, renderEditable, caretColor, caretTextPosition); + } + + final Color? floatingCursorColor = this.caretColor?.withOpacity(0.75); + // Floating Cursor. + if (floatingCursorRect == null || + floatingCursorColor == null || + !shouldPaint) { + return; + } + + canvas.drawRRect( + RRect.fromRectAndRadius(floatingCursorRect, _kFloatingCursorRadius), + floatingCursorPaint..color = floatingCursorColor, + ); + } + + @override + bool shouldRepaint(RenderEditablePainter? oldDelegate) { + if (identical(this, oldDelegate)) { + return false; + } + + if (oldDelegate == null) { + return shouldPaint; + } + return oldDelegate is! _CaretPainter || + oldDelegate.shouldPaint != shouldPaint || + oldDelegate.showRegularCaret != showRegularCaret || + oldDelegate.caretColor != caretColor || + oldDelegate.cursorRadius != cursorRadius || + oldDelegate.cursorOffset != cursorOffset || + oldDelegate.backgroundCursorColor != backgroundCursorColor || + oldDelegate.floatingCursorRect != floatingCursorRect; + } +} + +class _CompositeRenderEditablePainter extends RenderEditablePainter { + _CompositeRenderEditablePainter({required this.painters}); + + final List painters; + + @override + void addListener(VoidCallback listener) { + for (final RenderEditablePainter painter in painters) { + painter.addListener(listener); + } + } + + @override + void removeListener(VoidCallback listener) { + for (final RenderEditablePainter painter in painters) { + painter.removeListener(listener); + } + } + + // zmtzawqlp + @override + void paint(Canvas canvas, Size size, _RenderEditable renderEditable) { + for (final RenderEditablePainter painter in painters) { + painter.paint(canvas, size, renderEditable); + } + } + + @override + bool shouldRepaint(RenderEditablePainter? oldDelegate) { + if (identical(oldDelegate, this)) { + return false; + } + if (oldDelegate is! _CompositeRenderEditablePainter || + oldDelegate.painters.length != painters.length) { + return true; + } + + final Iterator oldPainters = + oldDelegate.painters.iterator; + final Iterator newPainters = painters.iterator; + while (oldPainters.moveNext() && newPainters.moveNext()) { + if (newPainters.current.shouldRepaint(oldPainters.current)) { + return true; + } + } + + return false; + } +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/official/widgets/editable_text.dart b/local_packages/extended_text_field-16.0.2/lib/src/official/widgets/editable_text.dart new file mode 100644 index 0000000..1bd4cfa --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/official/widgets/editable_text.dart @@ -0,0 +1,6031 @@ +// ignore_for_file: unused_field + +part of 'package:extended_text_field/src/extended/widgets/text_field.dart'; + +// Signature for a function that determines the target location of the given +// [TextPosition] after applying the given [TextBoundary]. +typedef _ApplyTextBoundary = TextPosition Function( + TextPosition, bool, TextBoundary); + +// The time it takes for the cursor to fade from fully opaque to fully +// transparent and vice versa. A full cursor blink, from transparent to opaque +// to transparent, is twice this duration. +const Duration _kCursorBlinkHalfPeriod = Duration(milliseconds: 500); + +// Number of cursor ticks during which the most recently entered character +// is shown in an obscured text field. +const int _kObscureShowLatestCharCursorTicks = 3; + +/// The default mime types to be used when allowedMimeTypes is not provided. +/// +/// The default value supports inserting images of any supported format. +const List kDefaultContentInsertionMimeTypes = [ + 'image/png', + 'image/bmp', + 'image/jpg', + 'image/tiff', + 'image/gif', + 'image/jpeg', + 'image/webp' +]; + +class _CompositionCallback extends SingleChildRenderObjectWidget { + const _CompositionCallback( + {required this.compositeCallback, required this.enabled, super.child}); + final CompositionCallback compositeCallback; + final bool enabled; + + @override + RenderObject createRenderObject(BuildContext context) { + return _RenderCompositionCallback(compositeCallback, enabled); + } + + @override + void updateRenderObject( + BuildContext context, _RenderCompositionCallback renderObject) { + super.updateRenderObject(context, renderObject); + // _EditableTextState always uses the same callback. + assert(renderObject.compositeCallback == compositeCallback); + renderObject.enabled = enabled; + } +} + +class _RenderCompositionCallback extends RenderProxyBox { + _RenderCompositionCallback(this.compositeCallback, this._enabled); + + final CompositionCallback compositeCallback; + VoidCallback? _cancelCallback; + + bool get enabled => _enabled; + bool _enabled = false; + set enabled(bool newValue) { + _enabled = newValue; + if (!newValue) { + _cancelCallback?.call(); + _cancelCallback = null; + } else if (_cancelCallback == null) { + markNeedsPaint(); + } + } + + @override + void paint(PaintingContext context, ui.Offset offset) { + if (enabled) { + _cancelCallback ??= context.addCompositionCallback(compositeCallback); + } + super.paint(context, offset); + } +} + +// A time-value pair that represents a key frame in an animation. +class _KeyFrame { + const _KeyFrame(this.time, this.value); + // Values extracted from iOS 15.4 UIKit. + static const List<_KeyFrame> iOSBlinkingCaretKeyFrames = <_KeyFrame>[ + _KeyFrame(0, 1), // 0 + _KeyFrame(0.5, 1), // 1 + _KeyFrame(0.5375, 0.75), // 2 + _KeyFrame(0.575, 0.5), // 3 + _KeyFrame(0.6125, 0.25), // 4 + _KeyFrame(0.65, 0), // 5 + _KeyFrame(0.85, 0), // 6 + _KeyFrame(0.8875, 0.25), // 7 + _KeyFrame(0.925, 0.5), // 8 + _KeyFrame(0.9625, 0.75), // 9 + _KeyFrame(1, 1), // 10 + ]; + + // The timing, in seconds, of the specified animation `value`. + final double time; + final double value; +} + +class _DiscreteKeyFrameSimulation extends Simulation { + _DiscreteKeyFrameSimulation.iOSBlinkingCaret() + : this._(_KeyFrame.iOSBlinkingCaretKeyFrames, 1); + _DiscreteKeyFrameSimulation._(this._keyFrames, this.maxDuration) + : assert(_keyFrames.isNotEmpty), + assert(_keyFrames.last.time <= maxDuration), + assert(() { + for (int i = 0; i < _keyFrames.length - 1; i += 1) { + if (_keyFrames[i].time > _keyFrames[i + 1].time) { + return false; + } + } + return true; + }(), 'The key frame sequence must be sorted by time.'); + + final double maxDuration; + + final List<_KeyFrame> _keyFrames; + + @override + double dx(double time) => 0; + + @override + bool isDone(double time) => time >= maxDuration; + + // The index of the KeyFrame corresponds to the most recent input `time`. + int _lastKeyFrameIndex = 0; + + @override + double x(double time) { + final int length = _keyFrames.length; + + // Perform a linear search in the sorted key frame list, starting from the + // last key frame found, since the input `time` usually monotonically + // increases by a small amount. + int searchIndex; + final int endIndex; + if (_keyFrames[_lastKeyFrameIndex].time > time) { + // The simulation may have restarted. Search within the index range + // [0, _lastKeyFrameIndex). + searchIndex = 0; + endIndex = _lastKeyFrameIndex; + } else { + searchIndex = _lastKeyFrameIndex; + endIndex = length; + } + + // Find the target key frame. Don't have to check (endIndex - 1): if + // (endIndex - 2) doesn't work we'll have to pick (endIndex - 1) anyways. + while (searchIndex < endIndex - 1) { + assert(_keyFrames[searchIndex].time <= time); + final _KeyFrame next = _keyFrames[searchIndex + 1]; + if (time < next.time) { + break; + } + searchIndex += 1; + } + + _lastKeyFrameIndex = searchIndex; + return _keyFrames[_lastKeyFrameIndex].value; + } +} + +/// [EditableText] +/// A basic text input field. +/// +/// This widget interacts with the [TextInput] service to let the user edit the +/// text it contains. It also provides scrolling, selection, and cursor +/// movement. +/// +/// The [EditableText] widget is a low-level widget that is intended as a +/// building block for custom widget sets. For a complete user experience, +/// consider using a [TextField] or [CupertinoTextField]. +/// +/// ## Handling User Input +/// +/// Currently the user may change the text this widget contains via keyboard or +/// the text selection menu. When the user inserted or deleted text, you will be +/// notified of the change and get a chance to modify the new text value: +/// +/// * The [inputFormatters] will be first applied to the user input. +/// +/// * The [controller]'s [TextEditingController.value] will be updated with the +/// formatted result, and the [controller]'s listeners will be notified. +/// +/// * The [onChanged] callback, if specified, will be called last. +/// +/// ## Input Actions +/// +/// A [TextInputAction] can be provided to customize the appearance of the +/// action button on the soft keyboard for Android and iOS. The default action +/// is [TextInputAction.done]. +/// +/// Many [TextInputAction]s are common between Android and iOS. However, if a +/// [textInputAction] is provided that is not supported by the current +/// platform in debug mode, an error will be thrown when the corresponding +/// EditableText receives focus. For example, providing iOS's "emergencyCall" +/// action when running on an Android device will result in an error when in +/// debug mode. In release mode, incompatible [TextInputAction]s are replaced +/// either with "unspecified" on Android, or "default" on iOS. Appropriate +/// [textInputAction]s can be chosen by checking the current platform and then +/// selecting the appropriate action. +/// +/// {@template flutter.widgets.EditableText.lifeCycle} +/// ## Lifecycle +/// +/// Upon completion of editing, like pressing the "done" button on the keyboard, +/// two actions take place: +/// +/// 1st: Editing is finalized. The default behavior of this step includes +/// an invocation of [onChanged]. That default behavior can be overridden. +/// See [onEditingComplete] for details. +/// +/// 2nd: [onSubmitted] is invoked with the user's input value. +/// +/// [onSubmitted] can be used to manually move focus to another input widget +/// when a user finishes with the currently focused input widget. +/// +/// When the widget has focus, it will prevent itself from disposing via +/// [AutomaticKeepAliveClientMixin.wantKeepAlive] in order to avoid losing the +/// selection. Removing the focus will allow it to be disposed. +/// {@endtemplate} +/// +/// Rather than using this widget directly, consider using [TextField], which +/// is a full-featured, material-design text input field with placeholder text, +/// labels, and [Form] integration. +/// +/// ## Text Editing [Intent]s and Their Default [Action]s +/// +/// This widget provides default [Action]s for handling common text editing +/// [Intent]s such as deleting, copying and pasting in the text field. These +/// [Action]s can be directly invoked using [Actions.invoke] or the +/// [Actions.maybeInvoke] method. The default text editing keyboard [Shortcuts], +/// typically declared in [DefaultTextEditingShortcuts], also use these +/// [Intent]s and [Action]s to perform the text editing operations they are +/// bound to. +/// +/// The default handling of a specific [Intent] can be overridden by placing an +/// [Actions] widget above this widget. See the [Action] class and the +/// [Action.overridable] constructor for more information on how a pre-defined +/// overridable [Action] can be overridden. +/// +/// ### Intents for Deleting Text and Their Default Behavior +/// +/// | **Intent Class** | **Default Behavior when there's selected text** | **Default Behavior when there is a [caret](https://en.wikipedia.org/wiki/Caret_navigation) (The selection is [TextSelection.collapsed])** | +/// | :------------------------------- | :--------------------------------------------------- | :----------------------------------------------------------------------- | +/// | [DeleteCharacterIntent] | Deletes the selected text | Deletes the user-perceived character before or after the caret location. | +/// | [DeleteToNextWordBoundaryIntent] | Deletes the selected text and the word before/after the selection's [TextSelection.extent] position | Deletes from the caret location to the previous or the next word boundary | +/// | [DeleteToLineBreakIntent] | Deletes the selected text, and deletes to the start/end of the line from the selection's [TextSelection.extent] position | Deletes from the caret location to the logical start or end of the current line | +/// +/// ### Intents for Moving the [Caret](https://en.wikipedia.org/wiki/Caret_navigation) +/// +/// | **Intent Class** | **Default Behavior when there's selected text** | **Default Behavior when there is a caret ([TextSelection.collapsed])** | +/// | :----------------------------------------------------------------------------------- | :--------------------------------------------------------------- | :---------------------------------------------------------------------- | +/// | [ExtendSelectionByCharacterIntent](`collapseSelection: true`) | Collapses the selection to the logical start/end of the selection | Moves the caret past the user-perceived character before or after the current caret location. | +/// | [ExtendSelectionToNextWordBoundaryIntent](`collapseSelection: true`) | Collapses the selection to the word boundary before/after the selection's [TextSelection.extent] position | Moves the caret to the previous/next word boundary. | +/// | [ExtendSelectionToNextWordBoundaryOrCaretLocationIntent](`collapseSelection: true`) | Collapses the selection to the word boundary before/after the selection's [TextSelection.extent] position, or [TextSelection.base], whichever is closest in the given direction | Moves the caret to the previous/next word boundary. | +/// | [ExtendSelectionToLineBreakIntent](`collapseSelection: true`) | Collapses the selection to the start/end of the line at the selection's [TextSelection.extent] position | Moves the caret to the start/end of the current line .| +/// | [ExtendSelectionVerticallyToAdjacentLineIntent](`collapseSelection: true`) | Collapses the selection to the position closest to the selection's [TextSelection.extent], on the previous/next adjacent line | Moves the caret to the closest position on the previous/next adjacent line. | +/// | [ExtendSelectionVerticallyToAdjacentPageIntent](`collapseSelection: true`) | Collapses the selection to the position closest to the selection's [TextSelection.extent], on the previous/next adjacent page | Moves the caret to the closest position on the previous/next adjacent page. | +/// | [ExtendSelectionToDocumentBoundaryIntent](`collapseSelection: true`) | Collapses the selection to the start/end of the document | Moves the caret to the start/end of the document. | +/// +/// #### Intents for Extending the Selection +/// +/// | **Intent Class** | **Default Behavior when there's selected text** | **Default Behavior when there is a caret ([TextSelection.collapsed])** | +/// | :----------------------------------------------------------------------------------- | :--------------------------------------------------------------- | :---------------------------------------------------------------------- | +/// | [ExtendSelectionByCharacterIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] past the user-perceived character before/after it | +/// | [ExtendSelectionToNextWordBoundaryIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the previous/next word boundary | +/// | [ExtendSelectionToNextWordBoundaryOrCaretLocationIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the previous/next word boundary, or [TextSelection.base] whichever is closest in the given direction | Moves the selection's [TextSelection.extent] to the previous/next word boundary. | +/// | [ExtendSelectionToLineBreakIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the start/end of the line | +/// | [ExtendSelectionVerticallyToAdjacentLineIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the closest position on the previous/next adjacent line | +/// | [ExtendSelectionVerticallyToAdjacentPageIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the closest position on the previous/next adjacent page | +/// | [ExtendSelectionToDocumentBoundaryIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the start/end of the document | +/// | [SelectAllTextIntent] | Selects the entire document | +/// +/// ### Other Intents +/// +/// | **Intent Class** | **Default Behavior** | +/// | :-------------------------------------- | :--------------------------------------------------- | +/// | [DoNothingAndStopPropagationTextIntent] | Does nothing in the input field, and prevents the key event from further propagating in the widget tree. | +/// | [ReplaceTextIntent] | Replaces the current [TextEditingValue] in the input field's [TextEditingController], and triggers all related user callbacks and [TextInputFormatter]s. | +/// | [UpdateSelectionIntent] | Updates the current selection in the input field's [TextEditingController], and triggers the [onSelectionChanged] callback. | +/// | [CopySelectionTextIntent] | Copies or cuts the selected text into the clipboard | +/// | [PasteTextIntent] | Inserts the current text in the clipboard after the caret location, or replaces the selected text if the selection is not collapsed. | +/// +/// ## Text Editing [Shortcuts] +/// +/// It's also possible to directly remap keyboard shortcuts to new [Intent]s by +/// inserting a [Shortcuts] widget above this in the widget tree. When using +/// [WidgetsApp], the large set of default text editing keyboard shortcuts are +/// declared near the top of the widget tree in [DefaultTextEditingShortcuts], +/// and any [Shortcuts] widget between it and this [EditableText] will override +/// those defaults. +/// +/// {@template flutter.widgets.editableText.shortcutsAndTextInput} +/// ### Interactions Between [Shortcuts] and Text Input +/// +/// Shortcuts prevent text input fields from receiving their keystrokes as text +/// input. For example, placing a [Shortcuts] widget in the widget tree above +/// a text input field and creating a shortcut for [LogicalKeyboardKey.keyA] +/// will prevent the field from receiving that key as text input. In other +/// words, typing key "A" into the field will trigger the shortcut and will not +/// insert a letter "a" into the field. +/// +/// This happens because of the way that key strokes are handled in Flutter. +/// When a keystroke is received in Flutter's engine, it first gives the +/// framework the opportunity to handle it as a raw key event through +/// [SystemChannels.keyEvent]. This is what [Shortcuts] listens to indirectly +/// through its [FocusNode]. If it is not handled, then it will proceed to try +/// handling it as text input through [SystemChannels.textInput], which is what +/// [EditableTextState] listens to through [TextInputClient]. +/// +/// This behavior, where a shortcut prevents text input into some field, can be +/// overridden by using another [Shortcuts] widget lower in the widget tree and +/// mapping the desired key stroke(s) to [DoNothingAndStopPropagationIntent]. +/// The key event will be reported as unhandled by the framework and will then +/// be sent as text input as usual. +/// {@endtemplate} +/// +/// ## Gesture Events Handling +/// +/// When [rendererIgnoresPointer] is false (the default), this widget provides +/// rudimentary, platform-agnostic gesture handling for user actions such as +/// tapping, long-pressing, and scrolling. +/// +/// To provide more complete gesture handling, including double-click to select +/// a word, drag selection, and platform-specific handling of gestures such as +/// long presses, consider setting [rendererIgnoresPointer] to true and using +/// [TextSelectionGestureDetectorBuilder]. +/// +/// {@template flutter.widgets.editableText.showCaretOnScreen} +/// ## Keep the caret visible when focused +/// +/// When focused, this widget will make attempts to keep the text area and its +/// caret (even when [showCursor] is `false`) visible, on these occasions: +/// +/// * When the user focuses this text field and it is not [readOnly]. +/// * When the user changes the selection of the text field, or changes the +/// text when the text field is not [readOnly]. +/// * When the virtual keyboard pops up. +/// {@endtemplate} +/// +/// ## Scrolling Considerations +/// +/// If this [EditableText] is not a descendant of [Scaffold] and is being used +/// within a [Scrollable] or nested [Scrollable]s, consider placing a +/// [ScrollNotificationObserver] above the root [Scrollable] that contains this +/// [EditableText] to ensure proper scroll coordination for [EditableText] and +/// its components like [TextSelectionOverlay]. +/// +/// {@template flutter.widgets.editableText.accessibility} +/// ## Troubleshooting Common Accessibility Issues +/// +/// ### Customizing User Input Accessibility Announcements +/// +/// To customize user input accessibility announcements triggered by text +/// changes, use [SemanticsService.announce] to make the desired +/// accessibility announcement. +/// +/// On iOS, the on-screen keyboard may announce the most recent input +/// incorrectly when a [TextInputFormatter] inserts a thousands separator to +/// a currency value text field. The following example demonstrates how to +/// suppress the default accessibility announcements by always announcing +/// the content of the text field as a US currency value (the `\$` inserts +/// a dollar sign, the `$newText` interpolates the `newText` variable): +/// +/// ```dart +/// onChanged: (String newText) { +/// if (newText.isNotEmpty) { +/// SemanticsService.announce('\$$newText', Directionality.of(context)); +/// } +/// } +/// ``` +/// +/// {@endtemplate} +/// +/// See also: +/// +/// * [TextField], which is a full-featured, material-design text input field +/// with placeholder text, labels, and [Form] integration. +class _EditableText extends StatefulWidget { + /// Creates a basic text input control. + /// + /// The [maxLines] property can be set to null to remove the restriction on + /// the number of lines. By default, it is one, meaning this is a single-line + /// text field. [maxLines] must be null or greater than zero. + /// + /// If [keyboardType] is not set or is null, its value will be inferred from + /// [autofillHints], if [autofillHints] is not empty. Otherwise it defaults to + /// [TextInputType.text] if [maxLines] is exactly one, and + /// [TextInputType.multiline] if [maxLines] is null or greater than one. + /// + /// The text cursor is not shown if [showCursor] is false or if [showCursor] + /// is null (the default) and [readOnly] is true. + _EditableText({ + super.key, + required this.controller, + required this.focusNode, + this.readOnly = false, + this.obscuringCharacter = '•', + this.obscureText = false, + this.autocorrect = true, + SmartDashesType? smartDashesType, + SmartQuotesType? smartQuotesType, + this.enableSuggestions = true, + required this.style, + StrutStyle? strutStyle, + required this.cursorColor, + required this.backgroundCursorColor, + this.textAlign = TextAlign.start, + this.textDirection, + this.locale, + this.textScaler, + this.maxLines = 1, + this.minLines, + this.expands = false, + this.forceLine = true, + this.textHeightBehavior, + this.textWidthBasis = TextWidthBasis.parent, + this.autofocus = false, + bool? showCursor, + this.showSelectionHandles = false, + this.selectionColor, + this.selectionControls, + TextInputType? keyboardType, + this.textInputAction, + this.textCapitalization = TextCapitalization.none, + this.onChanged, + this.onEditingComplete, + this.onSubmitted, + this.onAppPrivateCommand, + this.onSelectionChanged, + this.onSelectionHandleTapped, + this.groupId = EditableText, + this.onTapOutside, + List? inputFormatters, + this.mouseCursor, + this.rendererIgnoresPointer = false, + this.cursorWidth = 2.0, + this.cursorHeight, + this.cursorRadius, + this.cursorOpacityAnimates = false, + this.cursorOffset, + this.paintCursorAboveText = false, + this.selectionHeightStyle = ui.BoxHeightStyle.tight, + this.selectionWidthStyle = ui.BoxWidthStyle.tight, + this.scrollPadding = const EdgeInsets.all(20.0), + this.keyboardAppearance = Brightness.light, + this.dragStartBehavior = DragStartBehavior.start, + bool? enableInteractiveSelection, + this.scrollController, + this.scrollPhysics, + this.autocorrectionTextRectColor, + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + ToolbarOptions? toolbarOptions, + this.autofillHints = const [], + this.autofillClient, + this.clipBehavior = Clip.hardEdge, + this.restorationId, + this.scrollBehavior, + this.scribbleEnabled = true, + this.enableIMEPersonalizedLearning = true, + this.contentInsertionConfiguration, + this.contextMenuBuilder, + this.spellCheckConfiguration, + this.magnifierConfiguration = TextMagnifierConfiguration.disabled, + this.undoController, + }) : assert(obscuringCharacter.length == 1), + smartDashesType = smartDashesType ?? + (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled), + smartQuotesType = smartQuotesType ?? + (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled), + assert(minLines == null || minLines > 0), + assert( + (maxLines == null) || (minLines == null) || (maxLines >= minLines), + "minLines can't be greater than maxLines", + ), + assert( + !expands || (maxLines == null && minLines == null), + 'minLines and maxLines must be null when expands is true.', + ), + assert(!obscureText || maxLines == 1, + 'Obscured fields cannot be multiline.'), + enableInteractiveSelection = + enableInteractiveSelection ?? (!readOnly || !obscureText), + toolbarOptions = selectionControls is TextSelectionHandleControls && + toolbarOptions == null + ? ToolbarOptions.empty + : toolbarOptions ?? + (obscureText + ? (readOnly + // No point in even offering "Select All" in a read-only obscured + // field. + ? ToolbarOptions.empty + // Writable, but obscured. + : const ToolbarOptions( + selectAll: true, + paste: true, + )) + : (readOnly + // Read-only, not obscured. + ? const ToolbarOptions( + selectAll: true, + copy: true, + ) + // Writable, not obscured. + : const ToolbarOptions( + copy: true, + cut: true, + selectAll: true, + paste: true, + ))), + assert( + spellCheckConfiguration == null || + spellCheckConfiguration == + // zmtzawqlp + const _SpellCheckConfiguration.disabled() || + spellCheckConfiguration.misspelledTextStyle != null, + 'spellCheckConfiguration must specify a misspelledTextStyle if spell check behavior is desired', + ), + _strutStyle = strutStyle, + keyboardType = keyboardType ?? + _inferKeyboardType( + autofillHints: autofillHints, maxLines: maxLines), + inputFormatters = maxLines == 1 + ? [ + FilteringTextInputFormatter.singleLineFormatter, + ...inputFormatters ?? + const Iterable.empty(), + ] + : inputFormatters, + showCursor = showCursor ?? !readOnly; + + /// Controls the text being edited. + final TextEditingController controller; + + /// Controls whether this widget has keyboard focus. + final FocusNode focusNode; + + /// {@template flutter.widgets.editableText.obscuringCharacter} + /// Character used for obscuring text if [obscureText] is true. + /// + /// Must be only a single character. + /// + /// Defaults to the character U+2022 BULLET (•). + /// {@endtemplate} + final String obscuringCharacter; + + /// {@template flutter.widgets.editableText.obscureText} + /// Whether to hide the text being edited (e.g., for passwords). + /// + /// When this is set to true, all the characters in the text field are + /// replaced by [obscuringCharacter], and the text in the field cannot be + /// copied with copy or cut. If [readOnly] is also true, then the text cannot + /// be selected. + /// + /// Defaults to false. + /// {@endtemplate} + final bool obscureText; + + /// {@macro dart.ui.textHeightBehavior} + final TextHeightBehavior? textHeightBehavior; + + /// {@macro flutter.painting.textPainter.textWidthBasis} + final TextWidthBasis textWidthBasis; + + /// {@template flutter.widgets.editableText.readOnly} + /// Whether the text can be changed. + /// + /// When this is set to true, the text cannot be modified + /// by any shortcut or keyboard operation. The text is still selectable. + /// + /// Defaults to false. + /// {@endtemplate} + final bool readOnly; + + /// Whether the text will take the full width regardless of the text width. + /// + /// When this is set to false, the width will be based on text width, which + /// will also be affected by [textWidthBasis]. + /// + /// Defaults to true. + /// + /// See also: + /// + /// * [textWidthBasis], which controls the calculation of text width. + final bool forceLine; + + /// Configuration of toolbar options. + /// + /// By default, all options are enabled. If [readOnly] is true, paste and cut + /// will be disabled regardless. If [obscureText] is true, cut and copy will + /// be disabled regardless. If [readOnly] and [obscureText] are both true, + /// select all will also be disabled. + final ToolbarOptions toolbarOptions; + + /// Whether to show selection handles. + /// + /// When a selection is active, there will be two handles at each side of + /// boundary, or one handle if the selection is collapsed. The handles can be + /// dragged to adjust the selection. + /// + /// See also: + /// + /// * [showCursor], which controls the visibility of the cursor. + final bool showSelectionHandles; + + /// {@template flutter.widgets.editableText.showCursor} + /// Whether to show cursor. + /// + /// The cursor refers to the blinking caret when the [EditableText] is focused. + /// {@endtemplate} + /// + /// See also: + /// + /// * [showSelectionHandles], which controls the visibility of the selection handles. + final bool showCursor; + + /// {@template flutter.widgets.editableText.autocorrect} + /// Whether to enable autocorrection. + /// + /// Defaults to true. + /// {@endtemplate} + final bool autocorrect; + + /// {@macro flutter.services.TextInputConfiguration.smartDashesType} + final SmartDashesType smartDashesType; + + /// {@macro flutter.services.TextInputConfiguration.smartQuotesType} + final SmartQuotesType smartQuotesType; + + /// {@macro flutter.services.TextInputConfiguration.enableSuggestions} + final bool enableSuggestions; + + /// The text style to use for the editable text. + final TextStyle style; + + /// Controls the undo state of the current editable text. + /// + /// If null, this widget will create its own [UndoHistoryController]. + final UndoHistoryController? undoController; + + /// {@template flutter.widgets.editableText.strutStyle} + /// The strut style used for the vertical layout. + /// + /// [StrutStyle] is used to establish a predictable vertical layout. + /// Since fonts may vary depending on user input and due to font + /// fallback, [StrutStyle.forceStrutHeight] is enabled by default + /// to lock all lines to the height of the base [TextStyle], provided by + /// [style]. This ensures the typed text fits within the allotted space. + /// + /// If null, the strut used will inherit values from the [style] and will + /// have [StrutStyle.forceStrutHeight] set to true. When no [style] is + /// passed, the theme's [TextStyle] will be used to generate [strutStyle] + /// instead. + /// + /// To disable strut-based vertical alignment and allow dynamic vertical + /// layout based on the glyphs typed, use [StrutStyle.disabled]. + /// + /// Flutter's strut is based on [typesetting strut](https://en.wikipedia.org/wiki/Strut_(typesetting)) + /// and CSS's [line-height](https://www.w3.org/TR/CSS2/visudet.html#line-height). + /// {@endtemplate} + /// + /// Within editable text and text fields, [StrutStyle] will not use its standalone + /// default values, and will instead inherit omitted/null properties from the + /// [TextStyle] instead. See [StrutStyle.inheritFromTextStyle]. + StrutStyle get strutStyle { + if (_strutStyle == null) { + return StrutStyle.fromTextStyle(style, forceStrutHeight: true); + } + return _strutStyle.inheritFromTextStyle(style); + } + + final StrutStyle? _strutStyle; + + /// {@template flutter.widgets.editableText.textAlign} + /// How the text should be aligned horizontally. + /// + /// Defaults to [TextAlign.start]. + /// {@endtemplate} + final TextAlign textAlign; + + /// {@template flutter.widgets.editableText.textDirection} + /// The directionality of the text. + /// + /// This decides how [textAlign] values like [TextAlign.start] and + /// [TextAlign.end] are interpreted. + /// + /// This is also used to disambiguate how to render bidirectional text. For + /// example, if the text is an English phrase followed by a Hebrew phrase, + /// in a [TextDirection.ltr] context the English phrase will be on the left + /// and the Hebrew phrase to its right, while in a [TextDirection.rtl] + /// context, the English phrase will be on the right and the Hebrew phrase on + /// its left. + /// + /// Defaults to the ambient [Directionality], if any. + /// {@endtemplate} + final TextDirection? textDirection; + + /// {@template flutter.widgets.editableText.textCapitalization} + /// Configures how the platform keyboard will select an uppercase or + /// lowercase keyboard. + /// + /// Only supports text keyboards, other keyboard types will ignore this + /// configuration. Capitalization is locale-aware. + /// + /// Defaults to [TextCapitalization.none]. + /// + /// See also: + /// + /// * [TextCapitalization], for a description of each capitalization behavior. + /// + /// {@endtemplate} + final TextCapitalization textCapitalization; + + /// Used to select a font when the same Unicode character can + /// be rendered differently, depending on the locale. + /// + /// It's rarely necessary to set this property. By default its value + /// is inherited from the enclosing app with `Localizations.localeOf(context)`. + /// + /// See [RenderEditable.locale] for more information. + final Locale? locale; + + /// {@template flutter.widgets.editableText.textScaleFactor} + /// The number of font pixels for each logical pixel. + /// + /// For example, if the text scale factor is 1.5, text will be 50% larger than + /// the specified font size. + /// + /// Defaults to the [MediaQueryData.textScaleFactor] obtained from the ambient + /// [MediaQuery], or 1.0 if there is no [MediaQuery] in scope. + /// {@endtemplate} + + /// {@macro flutter.painting.textPainter.textScaler} + final TextScaler? textScaler; + + /// The color to use when painting the cursor. + final Color cursorColor; + + /// The color to use when painting the autocorrection Rect. + /// + /// For [CupertinoTextField]s, the value is set to the ambient + /// [CupertinoThemeData.primaryColor] with 20% opacity. For [TextField]s, the + /// value is null on non-iOS platforms and the same color used in [CupertinoTextField] + /// on iOS. + /// + /// Currently the autocorrection Rect only appears on iOS. + /// + /// Defaults to null, which disables autocorrection Rect painting. + final Color? autocorrectionTextRectColor; + + /// The color to use when painting the background cursor aligned with the text + /// while rendering the floating cursor. + /// + /// Typically this would be set to [CupertinoColors.inactiveGray]. + /// + /// See also: + /// + /// * [FloatingCursorDragState], which explains the floating cursor feature + /// in detail. + final Color backgroundCursorColor; + + /// {@template flutter.widgets.editableText.maxLines} + /// The maximum number of lines to show at one time, wrapping if necessary. + /// + /// This affects the height of the field itself and does not limit the number + /// of lines that can be entered into the field. + /// + /// If this is 1 (the default), the text will not wrap, but will scroll + /// horizontally instead. + /// + /// If this is null, there is no limit to the number of lines, and the text + /// container will start with enough vertical space for one line and + /// automatically grow to accommodate additional lines as they are entered, up + /// to the height of its constraints. + /// + /// If this is not null, the value must be greater than zero, and it will lock + /// the input to the given number of lines and take up enough horizontal space + /// to accommodate that number of lines. Setting [minLines] as well allows the + /// input to grow and shrink between the indicated range. + /// + /// The full set of behaviors possible with [minLines] and [maxLines] are as + /// follows. These examples apply equally to [TextField], [TextFormField], + /// [CupertinoTextField], and [EditableText]. + /// + /// Input that occupies a single line and scrolls horizontally as needed. + /// ```dart + /// const TextField() + /// ``` + /// + /// Input whose height grows from one line up to as many lines as needed for + /// the text that was entered. If a height limit is imposed by its parent, it + /// will scroll vertically when its height reaches that limit. + /// ```dart + /// const TextField(maxLines: null) + /// ``` + /// + /// The input's height is large enough for the given number of lines. If + /// additional lines are entered the input scrolls vertically. + /// ```dart + /// const TextField(maxLines: 2) + /// ``` + /// + /// Input whose height grows with content between a min and max. An infinite + /// max is possible with `maxLines: null`. + /// ```dart + /// const TextField(minLines: 2, maxLines: 4) + /// ``` + /// + /// See also: + /// + /// * [minLines], which sets the minimum number of lines visible. + /// {@endtemplate} + /// * [expands], which determines whether the field should fill the height of + /// its parent. + final int? maxLines; + + /// {@template flutter.widgets.editableText.minLines} + /// The minimum number of lines to occupy when the content spans fewer lines. + /// + /// This affects the height of the field itself and does not limit the number + /// of lines that can be entered into the field. + /// + /// If this is null (default), text container starts with enough vertical space + /// for one line and grows to accommodate additional lines as they are entered. + /// + /// This can be used in combination with [maxLines] for a varying set of behaviors. + /// + /// If the value is set, it must be greater than zero. If the value is greater + /// than 1, [maxLines] should also be set to either null or greater than + /// this value. + /// + /// When [maxLines] is set as well, the height will grow between the indicated + /// range of lines. When [maxLines] is null, it will grow as high as needed, + /// starting from [minLines]. + /// + /// A few examples of behaviors possible with [minLines] and [maxLines] are as follows. + /// These apply equally to [TextField], [TextFormField], [CupertinoTextField], + /// and [EditableText]. + /// + /// Input that always occupies at least 2 lines and has an infinite max. + /// Expands vertically as needed. + /// ```dart + /// TextField(minLines: 2) + /// ``` + /// + /// Input whose height starts from 2 lines and grows up to 4 lines at which + /// point the height limit is reached. If additional lines are entered it will + /// scroll vertically. + /// ```dart + /// const TextField(minLines:2, maxLines: 4) + /// ``` + /// + /// Defaults to null. + /// + /// See also: + /// + /// * [maxLines], which sets the maximum number of lines visible, and has + /// several examples of how minLines and maxLines interact to produce + /// various behaviors. + /// {@endtemplate} + /// * [expands], which determines whether the field should fill the height of + /// its parent. + final int? minLines; + + /// {@template flutter.widgets.editableText.expands} + /// Whether this widget's height will be sized to fill its parent. + /// + /// If set to true and wrapped in a parent widget like [Expanded] or + /// [SizedBox], the input will expand to fill the parent. + /// + /// [maxLines] and [minLines] must both be null when this is set to true, + /// otherwise an error is thrown. + /// + /// Defaults to false. + /// + /// See the examples in [maxLines] for the complete picture of how [maxLines], + /// [minLines], and [expands] interact to produce various behaviors. + /// + /// Input that matches the height of its parent: + /// ```dart + /// const Expanded( + /// child: TextField(maxLines: null, expands: true), + /// ) + /// ``` + /// {@endtemplate} + final bool expands; + + /// {@template flutter.widgets.editableText.autofocus} + /// Whether this text field should focus itself if nothing else is already + /// focused. + /// + /// If true, the keyboard will open as soon as this text field obtains focus. + /// Otherwise, the keyboard is only shown after the user taps the text field. + /// + /// Defaults to false. + /// {@endtemplate} + // See https://github.com/flutter/flutter/issues/7035 for the rationale for this + // keyboard behavior. + final bool autofocus; + + /// The color to use when painting the selection. + /// + /// If this property is null, this widget gets the selection color from the + /// [DefaultSelectionStyle]. + /// + /// For [CupertinoTextField]s, the value is set to the ambient + /// [CupertinoThemeData.primaryColor] with 20% opacity. For [TextField]s, the + /// value is set to the ambient [TextSelectionThemeData.selectionColor]. + final Color? selectionColor; + + /// {@template flutter.widgets.editableText.selectionControls} + /// Optional delegate for building the text selection handles. + /// + /// Historically, this field also controlled the toolbar. This is now handled + /// by [contextMenuBuilder] instead. However, for backwards compatibility, when + /// [selectionControls] is set to an object that does not mix in + /// [TextSelectionHandleControls], [contextMenuBuilder] is ignored and the + /// [TextSelectionControls.buildToolbar] method is used instead. + /// {@endtemplate} + /// + /// See also: + /// + /// * [CupertinoTextField], which wraps an [EditableText] and which shows the + /// selection toolbar upon user events that are appropriate on the iOS + /// platform. + /// * [TextField], a Material Design themed wrapper of [EditableText], which + /// shows the selection toolbar upon appropriate user events based on the + /// user's platform set in [ThemeData.platform]. + final TextSelectionControls? selectionControls; + + /// {@template flutter.widgets.editableText.keyboardType} + /// The type of keyboard to use for editing the text. + /// + /// Defaults to [TextInputType.text] if [maxLines] is one and + /// [TextInputType.multiline] otherwise. + /// {@endtemplate} + final TextInputType keyboardType; + + /// The type of action button to use with the soft keyboard. + final TextInputAction? textInputAction; + + /// {@template flutter.widgets.editableText.onChanged} + /// Called when the user initiates a change to the TextField's + /// value: when they have inserted or deleted text. + /// + /// This callback doesn't run when the TextField's text is changed + /// programmatically, via the TextField's [controller]. Typically it + /// isn't necessary to be notified of such changes, since they're + /// initiated by the app itself. + /// + /// To be notified of all changes to the TextField's text, cursor, + /// and selection, one can add a listener to its [controller] with + /// [TextEditingController.addListener]. + /// + /// [onChanged] is called before [onSubmitted] when user indicates completion + /// of editing, such as when pressing the "done" button on the keyboard. That + /// default behavior can be overridden. See [onEditingComplete] for details. + /// + /// {@tool dartpad} + /// This example shows how onChanged could be used to check the TextField's + /// current value each time the user inserts or deletes a character. + /// + /// ** See code in examples/api/lib/widgets/editable_text/editable_text.on_changed.0.dart ** + /// {@end-tool} + /// {@endtemplate} + /// + /// ## Handling emojis and other complex characters + /// {@template flutter.widgets.EditableText.onChanged} + /// It's important to always use + /// [characters](https://pub.dev/packages/characters) when dealing with user + /// input text that may contain complex characters. This will ensure that + /// extended grapheme clusters and surrogate pairs are treated as single + /// characters, as they appear to the user. + /// + /// For example, when finding the length of some user input, use + /// `string.characters.length`. Do NOT use `string.length` or even + /// `string.runes.length`. For the complex character "👨‍👩‍👦", this + /// appears to the user as a single character, and `string.characters.length` + /// intuitively returns 1. On the other hand, `string.length` returns 8, and + /// `string.runes.length` returns 5! + /// {@endtemplate} + /// + /// See also: + /// + /// * [inputFormatters], which are called before [onChanged] + /// runs and can validate and change ("format") the input value. + /// * [onEditingComplete], [onSubmitted], [onSelectionChanged]: + /// which are more specialized input change notifications. + /// * [TextEditingController], which implements the [Listenable] interface + /// and notifies its listeners on [TextEditingValue] changes. + final ValueChanged? onChanged; + + /// {@template flutter.widgets.editableText.onEditingComplete} + /// Called when the user submits editable content (e.g., user presses the "done" + /// button on the keyboard). + /// + /// The default implementation of [onEditingComplete] executes 2 different + /// behaviors based on the situation: + /// + /// - When a completion action is pressed, such as "done", "go", "send", or + /// "search", the user's content is submitted to the [controller] and then + /// focus is given up. + /// + /// - When a non-completion action is pressed, such as "next" or "previous", + /// the user's content is submitted to the [controller], but focus is not + /// given up because developers may want to immediately move focus to + /// another input widget within [onSubmitted]. + /// + /// Providing [onEditingComplete] prevents the aforementioned default behavior. + /// {@endtemplate} + final VoidCallback? onEditingComplete; + + /// {@template flutter.widgets.editableText.onSubmitted} + /// Called when the user indicates that they are done editing the text in the + /// field. + /// + /// By default, [onSubmitted] is called after [onChanged] when the user + /// has finalized editing; or, if the default behavior has been overridden, + /// after [onEditingComplete]. See [onEditingComplete] for details. + /// + /// ## Testing + /// The following is the recommended way to trigger [onSubmitted] in a test: + /// + /// ```dart + /// await tester.testTextInput.receiveAction(TextInputAction.done); + /// ``` + /// + /// Sending a `LogicalKeyboardKey.enter` via `tester.sendKeyEvent` will not + /// trigger [onSubmitted]. This is because on a real device, the engine + /// translates the enter key to a done action, but `tester.sendKeyEvent` sends + /// the key to the framework only. + /// {@endtemplate} + final ValueChanged? onSubmitted; + + /// {@template flutter.widgets.editableText.onAppPrivateCommand} + /// This is used to receive a private command from the input method. + /// + /// Called when the result of [TextInputClient.performPrivateCommand] is + /// received. + /// + /// This can be used to provide domain-specific features that are only known + /// between certain input methods and their clients. + /// + /// See also: + /// * [performPrivateCommand](https://developer.android.com/reference/android/view/inputmethod/InputConnection#performPrivateCommand\(java.lang.String,%20android.os.Bundle\)), + /// which is the Android documentation for performPrivateCommand, used to + /// send a command from the input method. + /// * [sendAppPrivateCommand](https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#sendAppPrivateCommand), + /// which is the Android documentation for sendAppPrivateCommand, used to + /// send a command to the input method. + /// {@endtemplate} + final AppPrivateCommandCallback? onAppPrivateCommand; + + /// {@template flutter.widgets.editableText.onSelectionChanged} + /// Called when the user changes the selection of text (including the cursor + /// location). + /// {@endtemplate} + final SelectionChangedCallback? onSelectionChanged; + + /// {@macro flutter.widgets.SelectionOverlay.onSelectionHandleTapped} + final VoidCallback? onSelectionHandleTapped; + + /// {@template flutter.widgets.editableText.groupId} + /// The group identifier for the [TextFieldTapRegion] of this text field. + /// + /// Text fields with the same group identifier share the same tap region. + /// Defaults to the type of [EditableText]. + /// + /// See also: + /// + /// * [TextFieldTapRegion], to give a [groupId] to a widget that is to be + /// included in a [EditableText]'s tap region that has [groupId] set. + /// {@endtemplate} + final Object groupId; + + /// {@template flutter.widgets.editableText.onTapOutside} + /// Called for each tap that occurs outside of the[TextFieldTapRegion] group + /// when the text field is focused. + /// + /// If this is null, [FocusNode.unfocus] will be called on the [focusNode] for + /// this text field when a [PointerDownEvent] is received on another part of + /// the UI. However, it will not unfocus as a result of mobile application + /// touch events (which does not include mouse clicks), to conform with the + /// platform conventions. To change this behavior, a callback may be set here + /// that operates differently from the default. + /// + /// When adding additional controls to a text field (for example, a spinner, a + /// button that copies the selected text, or modifies formatting), it is + /// helpful if tapping on that control doesn't unfocus the text field. In + /// order for an external widget to be considered as part of the text field + /// for the purposes of tapping "outside" of the field, wrap the control in a + /// [TextFieldTapRegion]. + /// + /// The [PointerDownEvent] passed to the function is the event that caused the + /// notification. It is possible that the event may occur outside of the + /// immediate bounding box defined by the text field, although it will be + /// within the bounding box of a [TextFieldTapRegion] member. + /// {@endtemplate} + /// + /// {@tool dartpad} + /// This example shows how to use a `TextFieldTapRegion` to wrap a set of + /// "spinner" buttons that increment and decrement a value in the [TextField] + /// without causing the text field to lose keyboard focus. + /// + /// This example includes a generic `SpinnerField` class that you can copy + /// into your own project and customize. + /// + /// ** See code in examples/api/lib/widgets/tap_region/text_field_tap_region.0.dart ** + /// {@end-tool} + /// + /// See also: + /// + /// * [TapRegion] for how the region group is determined. + final TapRegionCallback? onTapOutside; + + /// {@template flutter.widgets.editableText.inputFormatters} + /// Optional input validation and formatting overrides. + /// + /// Formatters are run in the provided order when the user changes the text + /// this widget contains. When this parameter changes, the new formatters will + /// not be applied until the next time the user inserts or deletes text. + /// Similar to the [onChanged] callback, formatters don't run when the text is + /// changed programmatically via [controller]. + /// + /// See also: + /// + /// * [TextEditingController], which implements the [Listenable] interface + /// and notifies its listeners on [TextEditingValue] changes. + /// {@endtemplate} + final List? inputFormatters; + + /// The cursor for a mouse pointer when it enters or is hovering over the + /// widget. + /// + /// If this property is null, [SystemMouseCursors.text] will be used. + /// + /// The [mouseCursor] is the only property of [EditableText] that controls the + /// appearance of the mouse pointer. All other properties related to "cursor" + /// stands for the text cursor, which is usually a blinking vertical line at + /// the editing position. + final MouseCursor? mouseCursor; + + /// Whether the caller will provide gesture handling (true), or if the + /// [EditableText] is expected to handle basic gestures (false). + /// + /// When this is false, the [EditableText] (or more specifically, the + /// [RenderEditable]) enables some rudimentary gestures (tap to position the + /// cursor, long-press to select all, and some scrolling behavior). + /// + /// These behaviors are sufficient for debugging purposes but are inadequate + /// for user-facing applications. To enable platform-specific behaviors, use a + /// [TextSelectionGestureDetectorBuilder] to wrap the [EditableText], and set + /// [rendererIgnoresPointer] to true. + /// + /// When [rendererIgnoresPointer] is true true, the [RenderEditable] created + /// by this widget will not handle pointer events. + /// + /// This property is false by default. + /// + /// See also: + /// + /// * [RenderEditable.ignorePointer], which implements this feature. + /// * [TextSelectionGestureDetectorBuilder], which implements platform-specific + /// gestures and behaviors. + final bool rendererIgnoresPointer; + + /// {@template flutter.widgets.editableText.cursorWidth} + /// How thick the cursor will be. + /// + /// Defaults to 2.0. + /// + /// The cursor will draw under the text. The cursor width will extend + /// to the right of the boundary between characters for left-to-right text + /// and to the left for right-to-left text. This corresponds to extending + /// downstream relative to the selected position. Negative values may be used + /// to reverse this behavior. + /// {@endtemplate} + final double cursorWidth; + + /// {@template flutter.widgets.editableText.cursorHeight} + /// How tall the cursor will be. + /// + /// If this property is null, [RenderEditable.preferredLineHeight] will be used. + /// {@endtemplate} + final double? cursorHeight; + + /// {@template flutter.widgets.editableText.cursorRadius} + /// How rounded the corners of the cursor should be. + /// + /// By default, the cursor has no radius. + /// {@endtemplate} + final Radius? cursorRadius; + + /// {@template flutter.widgets.editableText.cursorOpacityAnimates} + /// Whether the cursor will animate from fully transparent to fully opaque + /// during each cursor blink. + /// + /// By default, the cursor opacity will animate on iOS platforms and will not + /// animate on Android platforms. + /// {@endtemplate} + final bool cursorOpacityAnimates; + + ///{@macro flutter.rendering.RenderEditable.cursorOffset} + final Offset? cursorOffset; + + ///{@macro flutter.rendering.RenderEditable.paintCursorAboveText} + final bool paintCursorAboveText; + + /// Controls how tall the selection highlight boxes are computed to be. + /// + /// See [ui.BoxHeightStyle] for details on available styles. + final ui.BoxHeightStyle selectionHeightStyle; + + /// Controls how wide the selection highlight boxes are computed to be. + /// + /// See [ui.BoxWidthStyle] for details on available styles. + final ui.BoxWidthStyle selectionWidthStyle; + + /// The appearance of the keyboard. + /// + /// This setting is only honored on iOS devices. + /// + /// Defaults to [Brightness.light]. + final Brightness keyboardAppearance; + + /// {@template flutter.widgets.editableText.scrollPadding} + /// Configures padding to edges surrounding a [Scrollable] when the Textfield scrolls into view. + /// + /// When this widget receives focus and is not completely visible (for example scrolled partially + /// off the screen or overlapped by the keyboard) + /// then it will attempt to make itself visible by scrolling a surrounding [Scrollable], if one is present. + /// This value controls how far from the edges of a [Scrollable] the TextField will be positioned after the scroll. + /// + /// Defaults to EdgeInsets.all(20.0). + /// {@endtemplate} + final EdgeInsets scrollPadding; + + /// {@template flutter.widgets.editableText.enableInteractiveSelection} + /// Whether to enable user interface affordances for changing the + /// text selection. + /// + /// For example, setting this to true will enable features such as + /// long-pressing the TextField to select text and show the + /// cut/copy/paste menu, and tapping to move the text caret. + /// + /// When this is false, the text selection cannot be adjusted by + /// the user, text cannot be copied, and the user cannot paste into + /// the text field from the clipboard. + /// + /// Defaults to true. + /// {@endtemplate} + final bool enableInteractiveSelection; + + /// Setting this property to true makes the cursor stop blinking or fading + /// on and off once the cursor appears on focus. This property is useful for + /// testing purposes. + /// + /// It does not affect the necessity to focus the EditableText for the cursor + /// to appear in the first place. + /// + /// Defaults to false, resulting in a typical blinking cursor. + static bool debugDeterministicCursor = false; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + /// {@template flutter.widgets.editableText.scrollController} + /// The [ScrollController] to use when vertically scrolling the input. + /// + /// If null, it will instantiate a new ScrollController. + /// + /// See [Scrollable.controller]. + /// {@endtemplate} + final ScrollController? scrollController; + + /// {@template flutter.widgets.editableText.scrollPhysics} + /// The [ScrollPhysics] to use when vertically scrolling the input. + /// + /// If not specified, it will behave according to the current platform. + /// + /// See [Scrollable.physics]. + /// {@endtemplate} + /// + /// If an explicit [ScrollBehavior] is provided to [scrollBehavior], the + /// [ScrollPhysics] provided by that behavior will take precedence after + /// [scrollPhysics]. + final ScrollPhysics? scrollPhysics; + + /// {@template flutter.widgets.editableText.scribbleEnabled} + /// Whether iOS 14 Scribble features are enabled for this widget. + /// + /// Only available on iPads. + /// + /// Defaults to true. + /// {@endtemplate} + final bool scribbleEnabled; + + /// {@template flutter.widgets.editableText.selectionEnabled} + /// Same as [enableInteractiveSelection]. + /// + /// This getter exists primarily for consistency with + /// [RenderEditable.selectionEnabled]. + /// {@endtemplate} + bool get selectionEnabled => enableInteractiveSelection; + + /// {@template flutter.widgets.editableText.autofillHints} + /// A list of strings that helps the autofill service identify the type of this + /// text input. + /// + /// When set to null, this text input will not send its autofill information + /// to the platform, preventing it from participating in autofills triggered + /// by a different [AutofillClient], even if they're in the same + /// [AutofillScope]. Additionally, on Android and web, setting this to null + /// will disable autofill for this text field. + /// + /// The minimum platform SDK version that supports Autofill is API level 26 + /// for Android, and iOS 10.0 for iOS. + /// + /// Defaults to an empty list. + /// + /// ### Setting up iOS autofill: + /// + /// To provide the best user experience and ensure your app fully supports + /// password autofill on iOS, follow these steps: + /// + /// * Set up your iOS app's + /// [associated domains](https://developer.apple.com/documentation/safariservices/supporting_associated_domains_in_your_app). + /// * Some autofill hints only work with specific [keyboardType]s. For example, + /// [AutofillHints.name] requires [TextInputType.name] and [AutofillHints.email] + /// works only with [TextInputType.emailAddress]. Make sure the input field has a + /// compatible [keyboardType]. Empirically, [TextInputType.name] works well + /// with many autofill hints that are predefined on iOS. + /// + /// ### Troubleshooting Autofill + /// + /// Autofill service providers rely heavily on [autofillHints]. Make sure the + /// entries in [autofillHints] are supported by the autofill service currently + /// in use (the name of the service can typically be found in your mobile + /// device's system settings). + /// + /// #### Autofill UI refuses to show up when I tap on the text field + /// + /// Check the device's system settings and make sure autofill is turned on, + /// and there are available credentials stored in the autofill service. + /// + /// * iOS password autofill: Go to Settings -> Password, turn on "Autofill + /// Passwords", and add new passwords for testing by pressing the top right + /// "+" button. Use an arbitrary "website" if you don't have associated + /// domains set up for your app. As long as there's at least one password + /// stored, you should be able to see a key-shaped icon in the quick type + /// bar on the software keyboard, when a password related field is focused. + /// + /// * iOS contact information autofill: iOS seems to pull contact info from + /// the Apple ID currently associated with the device. Go to Settings -> + /// Apple ID (usually the first entry, or "Sign in to your iPhone" if you + /// haven't set up one on the device), and fill out the relevant fields. If + /// you wish to test more contact info types, try adding them in Contacts -> + /// My Card. + /// + /// * Android autofill: Go to Settings -> System -> Languages & input -> + /// Autofill service. Enable the autofill service of your choice, and make + /// sure there are available credentials associated with your app. + /// + /// #### I called `TextInput.finishAutofillContext` but the autofill save + /// prompt isn't showing + /// + /// * iOS: iOS may not show a prompt or any other visual indication when it + /// saves user password. Go to Settings -> Password and check if your new + /// password is saved. Neither saving password nor auto-generating strong + /// password works without properly setting up associated domains in your + /// app. To set up associated domains, follow the instructions in + /// . + /// + /// {@endtemplate} + /// {@macro flutter.services.AutofillConfiguration.autofillHints} + final Iterable? autofillHints; + + /// The [AutofillClient] that controls this input field's autofill behavior. + /// + /// When null, this widget's [EditableTextState] will be used as the + /// [AutofillClient]. This property may override [autofillHints]. + final AutofillClient? autofillClient; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + + /// Restoration ID to save and restore the scroll offset of the + /// [EditableText]. + /// + /// If a restoration id is provided, the [EditableText] will persist its + /// current scroll offset and restore it during state restoration. + /// + /// The scroll offset is persisted in a [RestorationBucket] claimed from + /// the surrounding [RestorationScope] using the provided restoration ID. + /// + /// Persisting and restoring the content of the [EditableText] is the + /// responsibility of the owner of the [controller], who may use a + /// [RestorableTextEditingController] for that purpose. + /// + /// See also: + /// + /// * [RestorationManager], which explains how state restoration works in + /// Flutter. + final String? restorationId; + + /// {@template flutter.widgets.shadow.scrollBehavior} + /// A [ScrollBehavior] that will be applied to this widget individually. + /// + /// Defaults to null, wherein the inherited [ScrollBehavior] is copied and + /// modified to alter the viewport decoration, like [Scrollbar]s. + /// {@endtemplate} + /// + /// [ScrollBehavior]s also provide [ScrollPhysics]. If an explicit + /// [ScrollPhysics] is provided in [scrollPhysics], it will take precedence, + /// followed by [scrollBehavior], and then the inherited ancestor + /// [ScrollBehavior]. + /// + /// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be + /// modified by default to only apply a [Scrollbar] if [maxLines] is greater + /// than 1. + final ScrollBehavior? scrollBehavior; + + /// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning} + final bool enableIMEPersonalizedLearning; + + /// {@template flutter.widgets.editableText.contentInsertionConfiguration} + /// Configuration of handler for media content inserted via the system input + /// method. + /// + /// Defaults to null in which case media content insertion will be disabled, + /// and the system will display a message informing the user that the text field + /// does not support inserting media content. + /// + /// Set [ContentInsertionConfiguration.onContentInserted] to provide a handler. + /// Additionally, set [ContentInsertionConfiguration.allowedMimeTypes] + /// to limit the allowable mime types for inserted content. + /// + /// {@tool dartpad} + /// + /// This example shows how to access the data for inserted content in your + /// `TextField`. + /// + /// ** See code in examples/api/lib/widgets/editable_text/editable_text.on_content_inserted.0.dart ** + /// {@end-tool} + /// + /// If [contentInsertionConfiguration] is not provided, by default + /// an empty list of mime types will be sent to the Flutter Engine. + /// A handler function must be provided in order to customize the allowable + /// mime types for inserted content. + /// + /// If rich content is inserted without a handler, the system will display + /// a message informing the user that the current text input does not support + /// inserting rich content. + /// {@endtemplate} + final ContentInsertionConfiguration? contentInsertionConfiguration; + + /// {@template flutter.widgets.EditableText.contextMenuBuilder} + /// Builds the text selection toolbar when requested by the user. + /// + /// The context menu is built when [EditableTextState.showToolbar] is called, + /// typically by one of the callbacks installed by the widget created by + /// [TextSelectionGestureDetectorBuilder.buildGestureDetector]. The widget + /// returned by [contextMenuBuilder] is passed to a [ContextMenuController]. + /// + /// If no callback is provided, no context menu will be shown. + /// + /// The [EditableTextContextMenuBuilder] signature used by the + /// [contextMenuBuilder] callback has two parameters, the [BuildContext] of + /// the [EditableText] and the [EditableTextState] of the [EditableText]. + /// + /// The [EditableTextState] has two properties that are especially useful when + /// building the widgets for the context menu: + /// + /// * [EditableTextState.contextMenuAnchors] specifies the desired anchor + /// position for the context menu. + /// + /// * [EditableTextState.contextMenuButtonItems] represents the buttons that + /// should typically be built for this widget (e.g. cut, copy, paste). + /// + /// The [TextSelectionToolbarLayoutDelegate] class may be particularly useful + /// in honoring the preferred anchor positions. + /// + /// For backwards compatibility, when [selectionControls] is set to an object + /// that does not mix in [TextSelectionHandleControls], [contextMenuBuilder] + /// is ignored and the [TextSelectionControls.buildToolbar] method is used + /// instead. + /// + /// {@tool dartpad} + /// This example shows how to customize the menu, in this case by keeping the + /// default buttons for the platform but modifying their appearance. + /// + /// ** See code in examples/api/lib/material/context_menu/editable_text_toolbar_builder.0.dart ** + /// {@end-tool} + /// + /// {@tool dartpad} + /// This example shows how to show a custom button only when an email address + /// is currently selected. + /// + /// ** See code in examples/api/lib/material/context_menu/editable_text_toolbar_builder.1.dart ** + /// {@end-tool} + /// + /// See also: + /// * [AdaptiveTextSelectionToolbar], which builds the default text selection + /// toolbar for the current platform, but allows customization of the + /// buttons. + /// * [AdaptiveTextSelectionToolbar.getAdaptiveButtons], which builds the + /// button Widgets for the current platform given + /// [ContextMenuButtonItem]s. + /// * [BrowserContextMenu], which allows the browser's context menu on web + /// to be disabled and Flutter-rendered context menus to appear. + /// {@endtemplate} + final EditableTextContextMenuBuilder? contextMenuBuilder; + + /// {@template flutter.widgets.EditableText.spellCheckConfiguration} + /// Configuration that details how spell check should be performed. + /// + /// Specifies the [SpellCheckService] used to spell check text input and the + /// [TextStyle] used to style text with misspelled words. + /// + /// If the [SpellCheckService] is left null, spell check is disabled by + /// default unless the [DefaultSpellCheckService] is supported, in which case + /// it is used. It is currently supported only on Android and iOS. + /// + /// If this configuration is left null, then spell check is disabled by default. + /// {@endtemplate} + // zmtzawqlp + final _SpellCheckConfiguration? spellCheckConfiguration; + + /// The configuration for the magnifier to use with selections in this text + /// field. + /// + /// {@macro flutter.widgets.magnifier.intro} + final TextMagnifierConfiguration magnifierConfiguration; + + bool get _userSelectionEnabled => + enableInteractiveSelection && (!readOnly || !obscureText); + + /// Returns the [ContextMenuButtonItem]s representing the buttons in this + /// platform's default selection menu for an editable field. + /// + /// For example, [EditableText] uses this to generate the default buttons for + /// its context menu. + /// + /// See also: + /// + /// * [EditableTextState.contextMenuButtonItems], which gives the + /// [ContextMenuButtonItem]s for a specific EditableText. + /// * [SelectableRegion.getSelectableButtonItems], which performs a similar + /// role but for content that is selectable but not editable. + /// * [AdaptiveTextSelectionToolbar], which builds the toolbar itself, and can + /// take a list of [ContextMenuButtonItem]s with + /// [AdaptiveTextSelectionToolbar.buttonItems]. + /// * [AdaptiveTextSelectionToolbar.getAdaptiveButtons], which builds the button + /// Widgets for the current platform given [ContextMenuButtonItem]s. + static List getEditableButtonItems({ + required final ClipboardStatus? clipboardStatus, + required final VoidCallback? onCopy, + required final VoidCallback? onCut, + required final VoidCallback? onPaste, + required final VoidCallback? onSelectAll, + required final VoidCallback? onLookUp, + required final VoidCallback? onSearchWeb, + required final VoidCallback? onShare, + required final VoidCallback? onLiveTextInput, + }) { + final List resultButtonItem = + []; + + // Configure button items with clipboard. + if (onPaste == null || clipboardStatus != ClipboardStatus.unknown) { + // If the paste button is enabled, don't render anything until the state + // of the clipboard is known, since it's used to determine if paste is + // shown. + + // On Android, the share button is before the select all button. + final bool showShareBeforeSelectAll = + defaultTargetPlatform == TargetPlatform.android; + + resultButtonItem.addAll([ + if (onCut != null) + ContextMenuButtonItem( + onPressed: onCut, + type: ContextMenuButtonType.cut, + ), + if (onCopy != null) + ContextMenuButtonItem( + onPressed: onCopy, + type: ContextMenuButtonType.copy, + ), + if (onPaste != null) + ContextMenuButtonItem( + onPressed: onPaste, + type: ContextMenuButtonType.paste, + ), + if (onShare != null && showShareBeforeSelectAll) + ContextMenuButtonItem( + onPressed: onShare, + type: ContextMenuButtonType.share, + ), + if (onSelectAll != null) + ContextMenuButtonItem( + onPressed: onSelectAll, + type: ContextMenuButtonType.selectAll, + ), + if (onLookUp != null) + ContextMenuButtonItem( + onPressed: onLookUp, + type: ContextMenuButtonType.lookUp, + ), + if (onSearchWeb != null) + ContextMenuButtonItem( + onPressed: onSearchWeb, + type: ContextMenuButtonType.searchWeb, + ), + if (onShare != null && !showShareBeforeSelectAll) + ContextMenuButtonItem( + onPressed: onShare, + type: ContextMenuButtonType.share, + ), + ]); + } + + // Config button items with Live Text. + if (onLiveTextInput != null) { + resultButtonItem.add(ContextMenuButtonItem( + onPressed: onLiveTextInput, + type: ContextMenuButtonType.liveTextInput, + )); + } + + return resultButtonItem; + } + + // Infer the keyboard type of an `EditableText` if it's not specified. + static TextInputType _inferKeyboardType({ + required Iterable? autofillHints, + required int? maxLines, + }) { + if (autofillHints == null || autofillHints.isEmpty) { + return maxLines == 1 ? TextInputType.text : TextInputType.multiline; + } + + final String effectiveHint = autofillHints.first; + + // On iOS oftentimes specifying a text content type is not enough to qualify + // the input field for autofill. The keyboard type also needs to be compatible + // with the content type. To get autofill to work by default on EditableText, + // the keyboard type inference on iOS is done differently from other platforms. + // + // The entries with "autofill not working" comments are the iOS text content + // types that should work with the specified keyboard type but won't trigger + // (even within a native app). Tested on iOS 13.5. + if (!kIsWeb) { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + const Map iOSKeyboardType = + { + AutofillHints.addressCity: TextInputType.name, + AutofillHints.addressCityAndState: + TextInputType.name, // Autofill not working. + AutofillHints.addressState: TextInputType.name, + AutofillHints.countryName: TextInputType.name, + AutofillHints.creditCardNumber: + TextInputType.number, // Couldn't test. + AutofillHints.email: TextInputType.emailAddress, + AutofillHints.familyName: TextInputType.name, + AutofillHints.fullStreetAddress: TextInputType.name, + AutofillHints.givenName: TextInputType.name, + AutofillHints.jobTitle: TextInputType.name, // Autofill not working. + AutofillHints.location: TextInputType.name, // Autofill not working. + AutofillHints.middleName: + TextInputType.name, // Autofill not working. + AutofillHints.name: TextInputType.name, + AutofillHints.namePrefix: + TextInputType.name, // Autofill not working. + AutofillHints.nameSuffix: + TextInputType.name, // Autofill not working. + AutofillHints.newPassword: TextInputType.text, + AutofillHints.newUsername: TextInputType.text, + AutofillHints.nickname: TextInputType.name, // Autofill not working. + AutofillHints.oneTimeCode: TextInputType.number, + AutofillHints.organizationName: + TextInputType.text, // Autofill not working. + AutofillHints.password: TextInputType.text, + AutofillHints.postalCode: TextInputType.name, + AutofillHints.streetAddressLine1: TextInputType.name, + AutofillHints.streetAddressLine2: + TextInputType.name, // Autofill not working. + AutofillHints.sublocality: + TextInputType.name, // Autofill not working. + AutofillHints.telephoneNumber: TextInputType.name, + AutofillHints.url: TextInputType.url, // Autofill not working. + AutofillHints.username: TextInputType.text, + }; + + final TextInputType? keyboardType = iOSKeyboardType[effectiveHint]; + if (keyboardType != null) { + return keyboardType; + } + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + break; + } + } + + if (maxLines != 1) { + return TextInputType.multiline; + } + + const Map inferKeyboardType = + { + AutofillHints.addressCity: TextInputType.streetAddress, + AutofillHints.addressCityAndState: TextInputType.streetAddress, + AutofillHints.addressState: TextInputType.streetAddress, + AutofillHints.birthday: TextInputType.datetime, + AutofillHints.birthdayDay: TextInputType.datetime, + AutofillHints.birthdayMonth: TextInputType.datetime, + AutofillHints.birthdayYear: TextInputType.datetime, + AutofillHints.countryCode: TextInputType.number, + AutofillHints.countryName: TextInputType.text, + AutofillHints.creditCardExpirationDate: TextInputType.datetime, + AutofillHints.creditCardExpirationDay: TextInputType.datetime, + AutofillHints.creditCardExpirationMonth: TextInputType.datetime, + AutofillHints.creditCardExpirationYear: TextInputType.datetime, + AutofillHints.creditCardFamilyName: TextInputType.name, + AutofillHints.creditCardGivenName: TextInputType.name, + AutofillHints.creditCardMiddleName: TextInputType.name, + AutofillHints.creditCardName: TextInputType.name, + AutofillHints.creditCardNumber: TextInputType.number, + AutofillHints.creditCardSecurityCode: TextInputType.number, + AutofillHints.creditCardType: TextInputType.text, + AutofillHints.email: TextInputType.emailAddress, + AutofillHints.familyName: TextInputType.name, + AutofillHints.fullStreetAddress: TextInputType.streetAddress, + AutofillHints.gender: TextInputType.text, + AutofillHints.givenName: TextInputType.name, + AutofillHints.impp: TextInputType.url, + AutofillHints.jobTitle: TextInputType.text, + AutofillHints.language: TextInputType.text, + AutofillHints.location: TextInputType.streetAddress, + AutofillHints.middleInitial: TextInputType.name, + AutofillHints.middleName: TextInputType.name, + AutofillHints.name: TextInputType.name, + AutofillHints.namePrefix: TextInputType.name, + AutofillHints.nameSuffix: TextInputType.name, + AutofillHints.newPassword: TextInputType.text, + AutofillHints.newUsername: TextInputType.text, + AutofillHints.nickname: TextInputType.text, + AutofillHints.oneTimeCode: TextInputType.text, + AutofillHints.organizationName: TextInputType.text, + AutofillHints.password: TextInputType.text, + AutofillHints.photo: TextInputType.text, + AutofillHints.postalAddress: TextInputType.streetAddress, + AutofillHints.postalAddressExtended: TextInputType.streetAddress, + AutofillHints.postalAddressExtendedPostalCode: TextInputType.number, + AutofillHints.postalCode: TextInputType.number, + AutofillHints.streetAddressLevel1: TextInputType.streetAddress, + AutofillHints.streetAddressLevel2: TextInputType.streetAddress, + AutofillHints.streetAddressLevel3: TextInputType.streetAddress, + AutofillHints.streetAddressLevel4: TextInputType.streetAddress, + AutofillHints.streetAddressLine1: TextInputType.streetAddress, + AutofillHints.streetAddressLine2: TextInputType.streetAddress, + AutofillHints.streetAddressLine3: TextInputType.streetAddress, + AutofillHints.sublocality: TextInputType.streetAddress, + AutofillHints.telephoneNumber: TextInputType.phone, + AutofillHints.telephoneNumberAreaCode: TextInputType.phone, + AutofillHints.telephoneNumberCountryCode: TextInputType.phone, + AutofillHints.telephoneNumberDevice: TextInputType.phone, + AutofillHints.telephoneNumberExtension: TextInputType.phone, + AutofillHints.telephoneNumberLocal: TextInputType.phone, + AutofillHints.telephoneNumberLocalPrefix: TextInputType.phone, + AutofillHints.telephoneNumberLocalSuffix: TextInputType.phone, + AutofillHints.telephoneNumberNational: TextInputType.phone, + AutofillHints.transactionAmount: + TextInputType.numberWithOptions(decimal: true), + AutofillHints.transactionCurrency: TextInputType.text, + AutofillHints.url: TextInputType.url, + AutofillHints.username: TextInputType.text, + }; + + return inferKeyboardType[effectiveHint] ?? TextInputType.text; + } + + @override + // zmtzawqlp + _EditableTextState createState() => _EditableTextState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add( + DiagnosticsProperty('controller', controller)); + properties.add(DiagnosticsProperty('focusNode', focusNode)); + properties.add(DiagnosticsProperty('obscureText', obscureText, + defaultValue: false)); + properties.add( + DiagnosticsProperty('readOnly', readOnly, defaultValue: false)); + properties.add(DiagnosticsProperty('autocorrect', autocorrect, + defaultValue: true)); + properties.add(EnumProperty( + 'smartDashesType', smartDashesType, + defaultValue: + obscureText ? SmartDashesType.disabled : SmartDashesType.enabled)); + properties.add(EnumProperty( + 'smartQuotesType', smartQuotesType, + defaultValue: + obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled)); + properties.add(DiagnosticsProperty( + 'enableSuggestions', enableSuggestions, + defaultValue: true)); + style.debugFillProperties(properties); + properties.add( + EnumProperty('textAlign', textAlign, defaultValue: null)); + properties.add(EnumProperty('textDirection', textDirection, + defaultValue: null)); + properties + .add(DiagnosticsProperty('locale', locale, defaultValue: null)); + properties.add(DiagnosticsProperty('textScaler', textScaler, + defaultValue: null)); + properties.add(IntProperty('maxLines', maxLines, defaultValue: 1)); + properties.add(IntProperty('minLines', minLines, defaultValue: null)); + properties.add( + DiagnosticsProperty('expands', expands, defaultValue: false)); + properties.add( + DiagnosticsProperty('autofocus', autofocus, defaultValue: false)); + properties.add(DiagnosticsProperty( + 'keyboardType', keyboardType, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'scrollController', scrollController, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'scrollPhysics', scrollPhysics, + defaultValue: null)); + properties.add(DiagnosticsProperty>( + 'autofillHints', autofillHints, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'textHeightBehavior', textHeightBehavior, + defaultValue: null)); + properties.add(DiagnosticsProperty('scribbleEnabled', scribbleEnabled, + defaultValue: true)); + properties.add(DiagnosticsProperty( + 'enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, + defaultValue: true)); + properties.add(DiagnosticsProperty( + 'enableInteractiveSelection', enableInteractiveSelection, + defaultValue: true)); + properties.add(DiagnosticsProperty( + 'undoController', undoController, + defaultValue: null)); + properties.add(DiagnosticsProperty<_SpellCheckConfiguration>( + 'spellCheckConfiguration', spellCheckConfiguration, + defaultValue: null)); + properties.add(DiagnosticsProperty>('contentCommitMimeTypes', + contentInsertionConfiguration?.allowedMimeTypes ?? const [], + defaultValue: contentInsertionConfiguration == null + ? const [] + : kDefaultContentInsertionMimeTypes)); + } +} + +/// State for a [EditableText]. +/// zmtzawqlp +class _EditableTextState extends State<_EditableText> + with + AutomaticKeepAliveClientMixin<_EditableText>, + WidgetsBindingObserver, + TickerProviderStateMixin<_EditableText>, + TextSelectionDelegate, + TextInputClient + implements AutofillClient { + Timer? _cursorTimer; + AnimationController get _cursorBlinkOpacityController { + return _backingCursorBlinkOpacityController ??= AnimationController( + vsync: this, + )..addListener(_onCursorColorTick); + } + + AnimationController? _backingCursorBlinkOpacityController; + late final Simulation _iosBlinkCursorSimulation = + _DiscreteKeyFrameSimulation.iOSBlinkingCaret(); + + final ValueNotifier _cursorVisibilityNotifier = + ValueNotifier(true); + final GlobalKey _editableKey = GlobalKey(); + + /// Detects whether the clipboard can paste. + final ClipboardStatusNotifier clipboardStatus = kIsWeb + // Web browsers will show a permission dialog when Clipboard.hasStrings is + // called. In an EditableText, this will happen before the paste button is + // clicked, often before the context menu is even shown. To avoid this + // poor user experience, always show the paste button on web. + ? _WebClipboardStatusNotifier() + : ClipboardStatusNotifier(); + + /// Detects whether the Live Text input is enabled. + /// + /// See also: + /// * [LiveText], where the availability of Live Text input can be obtained. + final LiveTextInputStatusNotifier? _liveTextInputStatus = + kIsWeb ? null : LiveTextInputStatusNotifier(); + + TextInputConnection? _textInputConnection; + bool get _hasInputConnection => _textInputConnection?.attached ?? false; + + /// zmtzawqlp + _TextSelectionOverlay? _selectionOverlay; + ScrollNotificationObserverState? _scrollNotificationObserver; + ({ + TextEditingValue value, + Rect selectionBounds + })? _dataWhenToolbarShowScheduled; + bool _listeningToScrollNotificationObserver = false; + + bool get _webContextMenuEnabled => kIsWeb && BrowserContextMenu.enabled; + + final GlobalKey _scrollableKey = GlobalKey(); + ScrollController? _internalScrollController; + ScrollController get _scrollController => + widget.scrollController ?? + (_internalScrollController ??= ScrollController()); + + final LayerLink _toolbarLayerLink = LayerLink(); + final LayerLink _startHandleLayerLink = LayerLink(); + final LayerLink _endHandleLayerLink = LayerLink(); + + bool _didAutoFocus = false; + + AutofillGroupState? _currentAutofillScope; + @override + AutofillScope? get currentAutofillScope => _currentAutofillScope; + + AutofillClient get _effectiveAutofillClient => widget.autofillClient ?? this; + + /// zmtzawqlp + late _SpellCheckConfiguration _spellCheckConfiguration; + late TextStyle _style; + + /// Configuration that determines how spell check will be performed. + /// + /// If possible, this configuration will contain a default for the + /// [SpellCheckService] if it is not otherwise specified. + /// + /// See also: + /// * [DefaultSpellCheckService], the spell check service used by default. + @visibleForTesting + + /// zmtzawqlp + _SpellCheckConfiguration get spellCheckConfiguration => + _spellCheckConfiguration; + + /// Whether or not spell check is enabled. + /// + /// Spell check is enabled when a [SpellCheckConfiguration] has been specified + /// for the widget. + bool get spellCheckEnabled => _spellCheckConfiguration.spellCheckEnabled; + + /// The most up-to-date spell check results for text input. + /// + /// These results will be updated via calls to spell check through a + /// [SpellCheckService] and used by this widget to build the [TextSpan] tree + /// for text input and menus for replacement suggestions of misspelled words. + SpellCheckResults? spellCheckResults; + + bool get _spellCheckResultsReceived => + spellCheckEnabled && + spellCheckResults != null && + spellCheckResults!.suggestionSpans.isNotEmpty; + + /// The text processing service used to retrieve the native text processing actions. + final ProcessTextService _processTextService = DefaultProcessTextService(); + + /// The list of native text processing actions provided by the engine. + final List _processTextActions = []; + + /// Whether to create an input connection with the platform for text editing + /// or not. + /// + /// Read-only input fields do not need a connection with the platform since + /// there's no need for text editing capabilities (e.g. virtual keyboard). + /// + /// On the web, we always need a connection because we want some browser + /// functionalities to continue to work on read-only input fields like: + /// + /// - Relevant context menu. + /// - cmd/ctrl+c shortcut to copy. + /// - cmd/ctrl+a to select all. + /// - Changing the selection using a physical keyboard. + bool get _shouldCreateInputConnection => kIsWeb || !widget.readOnly; + + // The time it takes for the floating cursor to snap to the text aligned + // cursor position after the user has finished placing it. + static const Duration _floatingCursorResetTime = Duration(milliseconds: 125); + + AnimationController? _floatingCursorResetController; + + Orientation? _lastOrientation; + + @override + bool get wantKeepAlive => widget.focusNode.hasFocus; + + Color get _cursorColor { + final double effectiveOpacity = math.min( + widget.cursorColor.alpha / 255.0, _cursorBlinkOpacityController.value); + return widget.cursorColor.withOpacity(effectiveOpacity); + } + + @override + bool get cutEnabled { + if (widget.selectionControls is! TextSelectionHandleControls) { + return widget.toolbarOptions.cut && + !widget.readOnly && + !widget.obscureText; + } + return !widget.readOnly && + !widget.obscureText && + !textEditingValue.selection.isCollapsed; + } + + @override + bool get copyEnabled { + if (widget.selectionControls is! TextSelectionHandleControls) { + return widget.toolbarOptions.copy && !widget.obscureText; + } + return !widget.obscureText && !textEditingValue.selection.isCollapsed; + } + + @override + bool get pasteEnabled { + if (widget.selectionControls is! TextSelectionHandleControls) { + return widget.toolbarOptions.paste && !widget.readOnly; + } + return !widget.readOnly && + (clipboardStatus.value == ClipboardStatus.pasteable); + } + + @override + bool get selectAllEnabled { + if (widget.selectionControls is! TextSelectionHandleControls) { + return widget.toolbarOptions.selectAll && + (!widget.readOnly || !widget.obscureText) && + widget.enableInteractiveSelection; + } + + if (!widget.enableInteractiveSelection || + (widget.readOnly && widget.obscureText)) { + return false; + } + + switch (defaultTargetPlatform) { + case TargetPlatform.macOS: + return false; + case TargetPlatform.iOS: + return textEditingValue.text.isNotEmpty && + textEditingValue.selection.isCollapsed; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + return textEditingValue.text.isNotEmpty && + !(textEditingValue.selection.start == 0 && + textEditingValue.selection.end == textEditingValue.text.length); + } + } + + @override + bool get lookUpEnabled { + if (defaultTargetPlatform != TargetPlatform.iOS) { + return false; + } + return !widget.obscureText && + !textEditingValue.selection.isCollapsed && + textEditingValue.selection.textInside(textEditingValue.text).trim() != + ''; + } + + @override + bool get searchWebEnabled { + if (defaultTargetPlatform != TargetPlatform.iOS) { + return false; + } + + return !widget.obscureText && + !textEditingValue.selection.isCollapsed && + textEditingValue.selection.textInside(textEditingValue.text).trim() != + ''; + } + + @override + bool get shareEnabled { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.iOS: + return !widget.obscureText && + !textEditingValue.selection.isCollapsed && + textEditingValue.selection + .textInside(textEditingValue.text) + .trim() != + ''; + case TargetPlatform.macOS: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + return false; + } + } + + @override + bool get liveTextInputEnabled { + return _liveTextInputStatus?.value == LiveTextInputStatus.enabled && + !widget.obscureText && + !widget.readOnly && + textEditingValue.selection.isCollapsed; + } + + void _onChangedClipboardStatus() { + setState(() { + // Inform the widget that the value of clipboardStatus has changed. + }); + } + + void _onChangedLiveTextInputStatus() { + setState(() { + // Inform the widget that the value of liveTextInputStatus has changed. + }); + } + + TextEditingValue get _textEditingValueforTextLayoutMetrics { + final Widget? editableWidget = _editableKey.currentContext?.widget; + if (editableWidget is! _Editable) { + throw StateError('_Editable must be mounted.'); + } + return editableWidget.value; + } + + /// Copy current selection to [Clipboard]. + @override + void copySelection(SelectionChangedCause cause) { + final TextSelection selection = textEditingValue.selection; + if (selection.isCollapsed || widget.obscureText) { + return; + } + final String text = textEditingValue.text; + Clipboard.setData(ClipboardData(text: selection.textInside(text))); + if (cause == SelectionChangedCause.toolbar) { + bringIntoView(textEditingValue.selection.extent); + hideToolbar(false); + + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + case TargetPlatform.linux: + case TargetPlatform.windows: + break; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + // Collapse the selection and hide the toolbar and handles. + userUpdateTextEditingValue( + TextEditingValue( + text: textEditingValue.text, + selection: TextSelection.collapsed( + offset: textEditingValue.selection.end), + ), + SelectionChangedCause.toolbar, + ); + } + } + clipboardStatus.update(); + } + + /// Cut current selection to [Clipboard]. + @override + void cutSelection(SelectionChangedCause cause) { + if (widget.readOnly || widget.obscureText) { + return; + } + final TextSelection selection = textEditingValue.selection; + final String text = textEditingValue.text; + if (selection.isCollapsed) { + return; + } + Clipboard.setData(ClipboardData(text: selection.textInside(text))); + _replaceText(ReplaceTextIntent(textEditingValue, '', selection, cause)); + if (cause == SelectionChangedCause.toolbar) { + // Schedule a call to bringIntoView() after renderEditable updates. + SchedulerBinding.instance.addPostFrameCallback((_) { + if (mounted) { + bringIntoView(textEditingValue.selection.extent); + } + }, debugLabel: 'EditableText.bringSelectionIntoView'); + hideToolbar(); + } + clipboardStatus.update(); + } + + bool get _allowPaste { + return !widget.readOnly && textEditingValue.selection.isValid; + } + + /// Paste text from [Clipboard]. + @override + Future pasteText(SelectionChangedCause cause) async { + if (!_allowPaste) { + return; + } + // Snapshot the input before using `await`. + // See https://github.com/flutter/flutter/issues/11427 + final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); + if (data == null) { + return; + } + _pasteText(cause, data.text!); + } + + void _pasteText(SelectionChangedCause cause, String text) { + if (!_allowPaste) { + return; + } + + // After the paste, the cursor should be collapsed and located after the + // pasted content. + final TextSelection selection = textEditingValue.selection; + final int lastSelectionIndex = + math.max(selection.baseOffset, selection.extentOffset); + final TextEditingValue collapsedTextEditingValue = + textEditingValue.copyWith( + selection: TextSelection.collapsed(offset: lastSelectionIndex), + ); + + userUpdateTextEditingValue( + collapsedTextEditingValue.replaced(selection, text), + cause, + ); + if (cause == SelectionChangedCause.toolbar) { + // Schedule a call to bringIntoView() after renderEditable updates. + SchedulerBinding.instance.addPostFrameCallback((_) { + if (mounted) { + bringIntoView(textEditingValue.selection.extent); + } + }, debugLabel: 'EditableText.bringSelectionIntoView'); + hideToolbar(); + } + } + + /// Select the entire text value. + @override + void selectAll(SelectionChangedCause cause) { + if (widget.readOnly && widget.obscureText) { + // If we can't modify it, and we can't copy it, there's no point in + // selecting it. + return; + } + userUpdateTextEditingValue( + textEditingValue.copyWith( + selection: TextSelection( + baseOffset: 0, extentOffset: textEditingValue.text.length), + ), + cause, + ); + + if (cause == SelectionChangedCause.toolbar) { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.iOS: + case TargetPlatform.fuchsia: + break; + case TargetPlatform.macOS: + case TargetPlatform.linux: + case TargetPlatform.windows: + hideToolbar(); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + bringIntoView(textEditingValue.selection.extent); + case TargetPlatform.macOS: + case TargetPlatform.iOS: + break; + } + } + } + + /// Look up the current selection, + /// as in the "Look Up" edit menu button on iOS. + /// + /// Currently this is only implemented for iOS. + /// + /// Throws an error if the selection is empty or collapsed. + Future lookUpSelection(SelectionChangedCause cause) async { + assert(!widget.obscureText); + + final String text = + textEditingValue.selection.textInside(textEditingValue.text); + if (widget.obscureText || text.isEmpty) { + return; + } + await SystemChannels.platform.invokeMethod( + 'LookUp.invoke', + text, + ); + } + + /// Launch a web search on the current selection, + /// as in the "Search Web" edit menu button on iOS. + /// + /// Currently this is only implemented for iOS. + /// + /// When 'obscureText' is true or the selection is empty, + /// this function will not do anything + Future searchWebForSelection(SelectionChangedCause cause) async { + assert(!widget.obscureText); + if (widget.obscureText) { + return; + } + + final String text = + textEditingValue.selection.textInside(textEditingValue.text); + if (text.isNotEmpty) { + await SystemChannels.platform.invokeMethod( + 'SearchWeb.invoke', + text, + ); + } + } + + /// Launch the share interface for the current selection, + /// as in the "Share..." edit menu button on iOS. + /// + /// Currently this is only implemented for iOS and Android. + /// + /// When 'obscureText' is true or the selection is empty, + /// this function will not do anything + Future shareSelection(SelectionChangedCause cause) async { + assert(!widget.obscureText); + if (widget.obscureText) { + return; + } + + final String text = + textEditingValue.selection.textInside(textEditingValue.text); + if (text.isNotEmpty) { + await SystemChannels.platform.invokeMethod( + 'Share.invoke', + text, + ); + } + } + + void _startLiveTextInput(SelectionChangedCause cause) { + if (!liveTextInputEnabled) { + return; + } + if (_hasInputConnection) { + LiveText.startLiveTextInput(); + } + if (cause == SelectionChangedCause.toolbar) { + hideToolbar(); + } + } + + /// Finds specified [SuggestionSpan] that matches the provided index using + /// binary search. + /// + /// See also: + /// + /// * [SpellCheckSuggestionsToolbar], the Material style spell check + /// suggestions toolbar that uses this method to render the correct + /// suggestions in the toolbar for a misspelled word. + SuggestionSpan? findSuggestionSpanAtCursorIndex(int cursorIndex) { + if (!_spellCheckResultsReceived || + spellCheckResults!.suggestionSpans.last.range.end < cursorIndex) { + // No spell check results have been received or the cursor index is out + // of range that suggestionSpans covers. + return null; + } + + final List suggestionSpans = + spellCheckResults!.suggestionSpans; + int leftIndex = 0; + int rightIndex = suggestionSpans.length - 1; + int midIndex = 0; + + while (leftIndex <= rightIndex) { + midIndex = ((leftIndex + rightIndex) / 2).floor(); + final int currentSpanStart = suggestionSpans[midIndex].range.start; + final int currentSpanEnd = suggestionSpans[midIndex].range.end; + + if (cursorIndex <= currentSpanEnd && cursorIndex >= currentSpanStart) { + return suggestionSpans[midIndex]; + } else if (cursorIndex <= currentSpanStart) { + rightIndex = midIndex - 1; + } else { + leftIndex = midIndex + 1; + } + } + return null; + } + + /// Infers the [_SpellCheckConfiguration] used to perform spell check. + /// + /// If spell check is enabled, this will try to infer a value for + /// the [SpellCheckService] if left unspecified. + /// zmtzawqlp + static _SpellCheckConfiguration _inferSpellCheckConfiguration( + _SpellCheckConfiguration? configuration) { + final SpellCheckService? spellCheckService = + configuration?.spellCheckService; + final bool spellCheckAutomaticallyDisabled = configuration == null || + configuration == const _SpellCheckConfiguration.disabled(); + final bool spellCheckServiceIsConfigured = spellCheckService != null || + spellCheckService == null && + WidgetsBinding + .instance.platformDispatcher.nativeSpellCheckServiceDefined; + if (spellCheckAutomaticallyDisabled || !spellCheckServiceIsConfigured) { + // Only enable spell check if a non-disabled configuration is provided + // and if that configuration does not specify a spell check service, + // a native spell checker must be supported. + assert(() { + if (!spellCheckAutomaticallyDisabled && + !spellCheckServiceIsConfigured) { + FlutterError.reportError( + FlutterErrorDetails( + exception: FlutterError( + 'Spell check was enabled with spellCheckConfiguration, but the ' + 'current platform does not have a supported spell check ' + 'service, and none was provided. Consider disabling spell ' + 'check for this platform or passing a SpellCheckConfiguration ' + 'with a specified spell check service.', + ), + library: 'widget library', + stack: StackTrace.current, + ), + ); + } + return true; + }()); + return const _SpellCheckConfiguration.disabled(); + } + + return configuration.copyWith( + spellCheckService: spellCheckService ?? DefaultSpellCheckService()); + } + + /// Returns the [ContextMenuButtonItem]s for the given [ToolbarOptions]. + @Deprecated( + 'Use `contextMenuBuilder` instead of `toolbarOptions`. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + List? buttonItemsForToolbarOptions( + [TargetPlatform? targetPlatform]) { + final ToolbarOptions toolbarOptions = widget.toolbarOptions; + if (toolbarOptions == ToolbarOptions.empty) { + return null; + } + return [ + if (toolbarOptions.cut && cutEnabled) + ContextMenuButtonItem( + onPressed: () { + cutSelection(SelectionChangedCause.toolbar); + }, + type: ContextMenuButtonType.cut, + ), + if (toolbarOptions.copy && copyEnabled) + ContextMenuButtonItem( + onPressed: () { + copySelection(SelectionChangedCause.toolbar); + }, + type: ContextMenuButtonType.copy, + ), + if (toolbarOptions.paste && pasteEnabled) + ContextMenuButtonItem( + onPressed: () { + pasteText(SelectionChangedCause.toolbar); + }, + type: ContextMenuButtonType.paste, + ), + if (toolbarOptions.selectAll && selectAllEnabled) + ContextMenuButtonItem( + onPressed: () { + selectAll(SelectionChangedCause.toolbar); + }, + type: ContextMenuButtonType.selectAll, + ), + ]; + } + + /// Gets the line heights at the start and end of the selection for the given + /// [_EditableTextState]. + /// + /// See also: + /// + /// * [TextSelectionToolbarAnchors.getSelectionRect], which depends on this + /// information. + ({double startGlyphHeight, double endGlyphHeight}) getGlyphHeights() { + final TextSelection selection = textEditingValue.selection; + + // Only calculate handle rects if the text in the previous frame + // is the same as the text in the current frame. This is done because + // widget.renderObject contains the renderEditable from the previous frame. + // If the text changed between the current and previous frames then + // widget.renderObject.getRectForComposingRange might fail. In cases where + // the current frame is different from the previous we fall back to + // renderObject.preferredLineHeight. + final InlineSpan span = renderEditable.text!; + final String prevText = span.toPlainText(); + final String currText = textEditingValue.text; + if (prevText != currText || !selection.isValid || selection.isCollapsed) { + return ( + startGlyphHeight: renderEditable.preferredLineHeight, + endGlyphHeight: renderEditable.preferredLineHeight, + ); + } + + final String selectedGraphemes = selection.textInside(currText); + final int firstSelectedGraphemeExtent = + selectedGraphemes.characters.first.length; + final Rect? startCharacterRect = + renderEditable.getRectForComposingRange(TextRange( + start: selection.start, + end: selection.start + firstSelectedGraphemeExtent, + )); + final int lastSelectedGraphemeExtent = + selectedGraphemes.characters.last.length; + final Rect? endCharacterRect = + renderEditable.getRectForComposingRange(TextRange( + start: selection.end - lastSelectedGraphemeExtent, + end: selection.end, + )); + return ( + startGlyphHeight: + startCharacterRect?.height ?? renderEditable.preferredLineHeight, + endGlyphHeight: + endCharacterRect?.height ?? renderEditable.preferredLineHeight, + ); + } + + /// {@template flutter.widgets.EditableText.getAnchors} + /// Returns the anchor points for the default context menu. + /// {@endtemplate} + /// + /// See also: + /// + /// * [contextMenuButtonItems], which provides the [ContextMenuButtonItem]s + /// for the default context menu buttons. + TextSelectionToolbarAnchors get contextMenuAnchors { + if (renderEditable.lastSecondaryTapDownPosition != null) { + return TextSelectionToolbarAnchors( + primaryAnchor: renderEditable.lastSecondaryTapDownPosition!, + ); + } + + final ( + startGlyphHeight: double startGlyphHeight, + endGlyphHeight: double endGlyphHeight + ) = getGlyphHeights(); + final TextSelection selection = textEditingValue.selection; + final List points = + renderEditable.getEndpointsForSelection(selection); + return TextSelectionToolbarAnchors.fromSelection( + renderBox: renderEditable, + startGlyphHeight: startGlyphHeight, + endGlyphHeight: endGlyphHeight, + selectionEndpoints: points, + ); + } + + /// Returns the [ContextMenuButtonItem]s representing the buttons in this + /// platform's default selection menu for [_EditableText]. + /// + /// See also: + /// + /// * [EditableText.getEditableButtonItems], which performs a similar role, + /// but for any editable field, not just specifically EditableText. + /// * [SelectableRegionState.contextMenuButtonItems], which performs a similar + /// role but for content that is selectable but not editable. + /// * [contextMenuAnchors], which provides the anchor points for the default + /// context menu. + /// * [AdaptiveTextSelectionToolbar], which builds the toolbar itself, and can + /// take a list of [ContextMenuButtonItem]s with + /// [AdaptiveTextSelectionToolbar.buttonItems]. + /// * [AdaptiveTextSelectionToolbar.getAdaptiveButtons], which builds the + /// button Widgets for the current platform given [ContextMenuButtonItem]s. + List get contextMenuButtonItems { + return buttonItemsForToolbarOptions() ?? + EditableText.getEditableButtonItems( + clipboardStatus: clipboardStatus.value, + onCopy: copyEnabled + ? () => copySelection(SelectionChangedCause.toolbar) + : null, + onCut: cutEnabled + ? () => cutSelection(SelectionChangedCause.toolbar) + : null, + onPaste: pasteEnabled + ? () => pasteText(SelectionChangedCause.toolbar) + : null, + onSelectAll: selectAllEnabled + ? () => selectAll(SelectionChangedCause.toolbar) + : null, + onLookUp: lookUpEnabled + ? () => lookUpSelection(SelectionChangedCause.toolbar) + : null, + onSearchWeb: searchWebEnabled + ? () => searchWebForSelection(SelectionChangedCause.toolbar) + : null, + onShare: shareEnabled + ? () => shareSelection(SelectionChangedCause.toolbar) + : null, + onLiveTextInput: liveTextInputEnabled + ? () => _startLiveTextInput(SelectionChangedCause.toolbar) + : null, + ) + ..addAll(_textProcessingActionButtonItems); + } + + List get _textProcessingActionButtonItems { + final List buttonItems = []; + final TextSelection selection = textEditingValue.selection; + if (widget.obscureText || !selection.isValid || selection.isCollapsed) { + return buttonItems; + } + + for (final ProcessTextAction action in _processTextActions) { + buttonItems.add(ContextMenuButtonItem( + label: action.label, + onPressed: () async { + final String selectedText = + selection.textInside(textEditingValue.text); + if (selectedText.isNotEmpty) { + final String? processedText = await _processTextService + .processTextAction(action.id, selectedText, widget.readOnly); + // If an activity does not return a modified version, just hide the toolbar. + // Otherwise use the result to replace the selected text. + if (processedText != null && _allowPaste) { + _pasteText(SelectionChangedCause.toolbar, processedText); + } else { + hideToolbar(); + } + } + }, + )); + } + return buttonItems; + } + + // State lifecycle: + + @override + void initState() { + super.initState(); + _liveTextInputStatus?.addListener(_onChangedLiveTextInputStatus); + clipboardStatus.addListener(_onChangedClipboardStatus); + widget.controller.addListener(_didChangeTextEditingValue); + widget.focusNode.addListener(_handleFocusChanged); + _cursorVisibilityNotifier.value = widget.showCursor; + // zmtzawqlp + // _spellCheckConfiguration = + // _inferSpellCheckConfiguration(widget.spellCheckConfiguration); + _initProcessTextActions(); + } + + /// Query the engine to initialize the list of text processing actions to show + /// in the text selection toolbar. + Future _initProcessTextActions() async { + _processTextActions.clear(); + _processTextActions.addAll(await _processTextService.queryTextActions()); + } + + // Whether `TickerMode.of(context)` is true and animations (like blinking the + // cursor) are supposed to run. + bool _tickersEnabled = true; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + _style = MediaQuery.boldTextOf(context) + ? widget.style.merge(const TextStyle(fontWeight: FontWeight.bold)) + : widget.style; + + final AutofillGroupState? newAutofillGroup = AutofillGroup.maybeOf(context); + if (currentAutofillScope != newAutofillGroup) { + _currentAutofillScope?.unregister(autofillId); + _currentAutofillScope = newAutofillGroup; + _currentAutofillScope?.register(_effectiveAutofillClient); + } + + if (!_didAutoFocus && widget.autofocus) { + _didAutoFocus = true; + SchedulerBinding.instance.addPostFrameCallback((_) { + if (mounted && renderEditable.hasSize) { + _flagInternalFocus(); + FocusScope.of(context).autofocus(widget.focusNode); + } + }, debugLabel: 'EditableText.autofocus'); + } + + // Restart or stop the blinking cursor when TickerMode changes. + final bool newTickerEnabled = TickerMode.of(context); + if (_tickersEnabled != newTickerEnabled) { + _tickersEnabled = newTickerEnabled; + if (_showBlinkingCursor) { + _startCursorBlink(); + } else if (!_tickersEnabled && _cursorTimer != null) { + _stopCursorBlink(); + } + } + + // Check for changes in viewId. + if (_hasInputConnection) { + final int newViewId = View.of(context).viewId; + if (newViewId != _viewId) { + _textInputConnection! + .updateConfig(_effectiveAutofillClient.textInputConfiguration); + } + } + + if (defaultTargetPlatform != TargetPlatform.iOS && + defaultTargetPlatform != TargetPlatform.android) { + return; + } + + // Hide the text selection toolbar on mobile when orientation changes. + final Orientation orientation = MediaQuery.orientationOf(context); + if (_lastOrientation == null) { + _lastOrientation = orientation; + return; + } + if (orientation != _lastOrientation) { + _lastOrientation = orientation; + if (defaultTargetPlatform == TargetPlatform.iOS) { + hideToolbar(false); + } + if (defaultTargetPlatform == TargetPlatform.android) { + hideToolbar(); + } + } + + if (_listeningToScrollNotificationObserver) { + // Only update subscription when we have previously subscribed to the + // scroll notification observer. We only subscribe to the scroll + // notification observer when the context menu is shown on platforms that + // support _platformSupportsFadeOnScroll. + _scrollNotificationObserver + ?.removeListener(_handleContextMenuOnParentScroll); + _scrollNotificationObserver = ScrollNotificationObserver.maybeOf(context); + _scrollNotificationObserver + ?.addListener(_handleContextMenuOnParentScroll); + } + } + + @override + + /// zmtzawqlp + void didUpdateWidget(_EditableText oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) { + oldWidget.controller.removeListener(_didChangeTextEditingValue); + widget.controller.addListener(_didChangeTextEditingValue); + _updateRemoteEditingValueIfNeeded(); + } + + if (_selectionOverlay != null && + (widget.contextMenuBuilder != oldWidget.contextMenuBuilder || + widget.selectionControls != oldWidget.selectionControls || + widget.onSelectionHandleTapped != + oldWidget.onSelectionHandleTapped || + widget.dragStartBehavior != oldWidget.dragStartBehavior || + widget.magnifierConfiguration != + oldWidget.magnifierConfiguration)) { + final bool shouldShowToolbar = _selectionOverlay!.toolbarIsVisible; + final bool shouldShowHandles = _selectionOverlay!.handlesVisible; + _selectionOverlay!.dispose(); + _selectionOverlay = _createSelectionOverlay(); + if (shouldShowToolbar || shouldShowHandles) { + SchedulerBinding.instance.addPostFrameCallback((Duration _) { + if (shouldShowToolbar) { + _selectionOverlay!.showToolbar(); + } + if (shouldShowHandles) { + _selectionOverlay!.showHandles(); + } + }); + } + } else if (widget.controller.selection != oldWidget.controller.selection) { + _selectionOverlay?.update(_value); + } + _selectionOverlay?.handlesVisible = widget.showSelectionHandles; + + if (widget.autofillClient != oldWidget.autofillClient) { + _currentAutofillScope + ?.unregister(oldWidget.autofillClient?.autofillId ?? autofillId); + _currentAutofillScope?.register(_effectiveAutofillClient); + } + + if (widget.focusNode != oldWidget.focusNode) { + oldWidget.focusNode.removeListener(_handleFocusChanged); + widget.focusNode.addListener(_handleFocusChanged); + updateKeepAlive(); + } + + if (!_shouldCreateInputConnection) { + _closeInputConnectionIfNeeded(); + } else if (oldWidget.readOnly && _hasFocus) { + // _openInputConnection must be called after layout information is available. + // See https://github.com/flutter/flutter/issues/126312 + SchedulerBinding.instance.addPostFrameCallback((Duration _) { + _openInputConnection(); + }, debugLabel: 'EditableText.openInputConnection'); + } + + if (kIsWeb && _hasInputConnection) { + if (oldWidget.readOnly != widget.readOnly) { + _textInputConnection! + .updateConfig(_effectiveAutofillClient.textInputConfiguration); + } + } + + if (_hasInputConnection) { + if (oldWidget.obscureText != widget.obscureText) { + _textInputConnection! + .updateConfig(_effectiveAutofillClient.textInputConfiguration); + } + } + + if (widget.style != oldWidget.style) { + // The _textInputConnection will pick up the new style when it attaches in + // _openInputConnection. + _style = MediaQuery.boldTextOf(context) + ? widget.style.merge(const TextStyle(fontWeight: FontWeight.bold)) + : widget.style; + if (_hasInputConnection) { + _textInputConnection!.setStyle( + fontFamily: _style.fontFamily, + fontSize: _style.fontSize, + fontWeight: _style.fontWeight, + textDirection: _textDirection, + textAlign: widget.textAlign, + ); + } + } + + if (widget.showCursor != oldWidget.showCursor) { + _startOrStopCursorTimerIfNeeded(); + } + final bool canPaste = + widget.selectionControls is TextSelectionHandleControls + ? pasteEnabled + : widget.selectionControls?.canPaste(this) ?? false; + if (widget.selectionEnabled && pasteEnabled && canPaste) { + clipboardStatus.update(); + } + } + + void _disposeScrollNotificationObserver() { + _listeningToScrollNotificationObserver = false; + if (_scrollNotificationObserver != null) { + _scrollNotificationObserver! + .removeListener(_handleContextMenuOnParentScroll); + _scrollNotificationObserver = null; + } + } + + @override + void dispose() { + _internalScrollController?.dispose(); + _currentAutofillScope?.unregister(autofillId); + widget.controller.removeListener(_didChangeTextEditingValue); + _floatingCursorResetController?.dispose(); + _floatingCursorResetController = null; + _closeInputConnectionIfNeeded(); + assert(!_hasInputConnection); + _cursorTimer?.cancel(); + _cursorTimer = null; + _backingCursorBlinkOpacityController?.dispose(); + _backingCursorBlinkOpacityController = null; + _selectionOverlay?.dispose(); + _selectionOverlay = null; + widget.focusNode.removeListener(_handleFocusChanged); + WidgetsBinding.instance.removeObserver(this); + _liveTextInputStatus?.removeListener(_onChangedLiveTextInputStatus); + _liveTextInputStatus?.dispose(); + clipboardStatus.removeListener(_onChangedClipboardStatus); + clipboardStatus.dispose(); + _cursorVisibilityNotifier.dispose(); + FocusManager.instance.removeListener(_unflagInternalFocus); + _disposeScrollNotificationObserver(); + super.dispose(); + assert(_batchEditDepth <= 0, 'unfinished batch edits: $_batchEditDepth'); + } + + // TextInputClient implementation: + + /// The last known [TextEditingValue] of the platform text input plugin. + /// + /// This value is updated when the platform text input plugin sends a new + /// update via [updateEditingValue], or when [EditableText] calls + /// [TextInputConnection.setEditingState] to overwrite the platform text input + /// plugin's [TextEditingValue]. + /// + /// Used in [_updateRemoteEditingValueIfNeeded] to determine whether the + /// remote value is outdated and needs updating. + TextEditingValue? _lastKnownRemoteTextEditingValue; + + @override + TextEditingValue get currentTextEditingValue => _value; + + @override + void updateEditingValue(TextEditingValue value) { + // This method handles text editing state updates from the platform text + // input plugin. The [EditableText] may not have the focus or an open input + // connection, as autofill can update a disconnected [EditableText]. + + // Since we still have to support keyboard select, this is the best place + // to disable text updating. + if (!_shouldCreateInputConnection) { + return; + } + + if (_checkNeedsAdjustAffinity(value)) { + value = value.copyWith( + selection: + value.selection.copyWith(affinity: _value.selection.affinity)); + } + + if (widget.readOnly) { + // In the read-only case, we only care about selection changes, and reject + // everything else. + value = _value.copyWith(selection: value.selection); + } + _lastKnownRemoteTextEditingValue = value; + + if (value == _value) { + // This is possible, for example, when the numeric keyboard is input, + // the engine will notify twice for the same value. + // Track at https://github.com/flutter/flutter/issues/65811 + return; + } + + if (value.text == _value.text && value.composing == _value.composing) { + // `selection` is the only change. + SelectionChangedCause cause; + if (_textInputConnection?.scribbleInProgress ?? false) { + cause = SelectionChangedCause.scribble; + } else if (_pointOffsetOrigin != null) { + // For floating cursor selection when force pressing the space bar. + cause = SelectionChangedCause.forcePress; + } else { + cause = SelectionChangedCause.keyboard; + } + _handleSelectionChanged(value.selection, cause); + } else { + if (value.text != _value.text) { + // Hide the toolbar if the text was changed, but only hide the toolbar + // overlay; the selection handle's visibility will be handled + // by `_handleSelectionChanged`. https://github.com/flutter/flutter/issues/108673 + hideToolbar(false); + } + _currentPromptRectRange = null; + + final bool revealObscuredInput = _hasInputConnection && + widget.obscureText && + WidgetsBinding.instance.platformDispatcher.brieflyShowPassword && + value.text.length == _value.text.length + 1; + + _obscureShowCharTicksPending = + revealObscuredInput ? _kObscureShowLatestCharCursorTicks : 0; + _obscureLatestCharIndex = + revealObscuredInput ? _value.selection.baseOffset : null; + _formatAndSetValue(value, SelectionChangedCause.keyboard); + } + + if (_showBlinkingCursor && _cursorTimer != null) { + // To keep the cursor from blinking while typing, restart the timer here. + _stopCursorBlink(resetCharTicks: false); + _startCursorBlink(); + } + + // Wherever the value is changed by the user, schedule a showCaretOnScreen + // to make sure the user can see the changes they just made. Programmatic + // changes to `textEditingValue` do not trigger the behavior even if the + // text field is focused. + _scheduleShowCaretOnScreen(withAnimation: true); + } + + bool _checkNeedsAdjustAffinity(TextEditingValue value) { + // Trust the engine affinity if the text changes or selection changes. + return value.text == _value.text && + value.selection.isCollapsed == _value.selection.isCollapsed && + value.selection.start == _value.selection.start && + value.selection.affinity != _value.selection.affinity; + } + + @override + void performAction(TextInputAction action) { + switch (action) { + case TextInputAction.newline: + // If this is a multiline EditableText, do nothing for a "newline" + // action; The newline is already inserted. Otherwise, finalize + // editing. + if (!_isMultiline) { + _finalizeEditing(action, shouldUnfocus: true); + } + case TextInputAction.done: + case TextInputAction.go: + case TextInputAction.next: + case TextInputAction.previous: + case TextInputAction.search: + case TextInputAction.send: + _finalizeEditing(action, shouldUnfocus: true); + case TextInputAction.continueAction: + case TextInputAction.emergencyCall: + case TextInputAction.join: + case TextInputAction.none: + case TextInputAction.route: + case TextInputAction.unspecified: + // Finalize editing, but don't give up focus because this keyboard + // action does not imply the user is done inputting information. + _finalizeEditing(action, shouldUnfocus: false); + } + } + + @override + void performPrivateCommand(String action, Map data) { + widget.onAppPrivateCommand?.call(action, data); + } + + @override + void insertContent(KeyboardInsertedContent content) { + assert(widget.contentInsertionConfiguration?.allowedMimeTypes + .contains(content.mimeType) ?? + false); + widget.contentInsertionConfiguration?.onContentInserted.call(content); + } + + // The original position of the caret on FloatingCursorDragState.start. + Offset? _startCaretCenter; + + // The most recent text position as determined by the location of the floating + // cursor. + TextPosition? _lastTextPosition; + + // The offset of the floating cursor as determined from the start call. + Offset? _pointOffsetOrigin; + + // The most recent position of the floating cursor. + Offset? _lastBoundedOffset; + + // Because the center of the cursor is preferredLineHeight / 2 below the touch + // origin, but the touch origin is used to determine which line the cursor is + // on, we need this offset to correctly render and move the cursor. + Offset get _floatingCursorOffset => + Offset(0, renderEditable.preferredLineHeight / 2); + + @override + void updateFloatingCursor(RawFloatingCursorPoint point) { + _floatingCursorResetController ??= AnimationController( + vsync: this, + )..addListener(_onFloatingCursorResetTick); + switch (point.state) { + case FloatingCursorDragState.Start: + if (_floatingCursorResetController!.isAnimating) { + _floatingCursorResetController!.stop(); + _onFloatingCursorResetTick(); + } + // Stop cursor blinking and making it visible. + _stopCursorBlink(resetCharTicks: false); + _cursorBlinkOpacityController.value = 1.0; + // We want to send in points that are centered around a (0,0) origin, so + // we cache the position. + _pointOffsetOrigin = point.offset; + + final Offset startCaretCenter; + final TextPosition currentTextPosition; + final bool shouldResetOrigin; + // Only non-null when starting a floating cursor via long press. + if (point.startLocation != null) { + shouldResetOrigin = false; + (startCaretCenter, currentTextPosition) = point.startLocation!; + } else { + shouldResetOrigin = true; + currentTextPosition = TextPosition( + offset: renderEditable.selection!.baseOffset, + affinity: renderEditable.selection!.affinity); + startCaretCenter = + renderEditable.getLocalRectForCaret(currentTextPosition).center; + } + + _startCaretCenter = startCaretCenter; + _lastBoundedOffset = + renderEditable.calculateBoundedFloatingCursorOffset( + _startCaretCenter! - _floatingCursorOffset, + shouldResetOrigin: shouldResetOrigin); + _lastTextPosition = currentTextPosition; + renderEditable.setFloatingCursor( + point.state, _lastBoundedOffset!, _lastTextPosition!); + case FloatingCursorDragState.Update: + final Offset centeredPoint = point.offset! - _pointOffsetOrigin!; + final Offset rawCursorOffset = + _startCaretCenter! + centeredPoint - _floatingCursorOffset; + + _lastBoundedOffset = renderEditable + .calculateBoundedFloatingCursorOffset(rawCursorOffset); + _lastTextPosition = renderEditable.getPositionForPoint(renderEditable + .localToGlobal(_lastBoundedOffset! + _floatingCursorOffset)); + renderEditable.setFloatingCursor( + point.state, _lastBoundedOffset!, _lastTextPosition!); + case FloatingCursorDragState.End: + // Resume cursor blinking. + _startCursorBlink(); + // We skip animation if no update has happened. + if (_lastTextPosition != null && _lastBoundedOffset != null) { + _floatingCursorResetController!.value = 0.0; + _floatingCursorResetController!.animateTo(1.0, + duration: _floatingCursorResetTime, curve: Curves.decelerate); + } + } + } + + void _onFloatingCursorResetTick() { + final Offset finalPosition = + renderEditable.getLocalRectForCaret(_lastTextPosition!).centerLeft - + _floatingCursorOffset; + if (_floatingCursorResetController!.isCompleted) { + renderEditable.setFloatingCursor( + FloatingCursorDragState.End, finalPosition, _lastTextPosition!); + // During a floating cursor's move gesture (1 finger), a cursor is + // animated only visually, without actually updating the selection. + // Only after move gesture is complete, this function will be called + // to actually update the selection to the new cursor location with + // zero selection length. + + // However, During a floating cursor's selection gesture (2 fingers), the + // selection is constantly updated by the engine throughout the gesture. + // Thus when the gesture is complete, we should not update the selection + // to the cursor location with zero selection length, because that would + // overwrite the selection made by floating cursor selection. + + // Here we use `isCollapsed` to distinguish between floating cursor's + // move gesture (1 finger) vs selection gesture (2 fingers), as + // the engine does not provide information other than notifying a + // new selection during with selection gesture (2 fingers). + if (renderEditable.selection!.isCollapsed) { + // The cause is technically the force cursor, but the cause is listed as tap as the desired functionality is the same. + _handleSelectionChanged(TextSelection.fromPosition(_lastTextPosition!), + SelectionChangedCause.forcePress); + } + _startCaretCenter = null; + _lastTextPosition = null; + _pointOffsetOrigin = null; + _lastBoundedOffset = null; + } else { + final double lerpValue = _floatingCursorResetController!.value; + final double lerpX = + ui.lerpDouble(_lastBoundedOffset!.dx, finalPosition.dx, lerpValue)!; + final double lerpY = + ui.lerpDouble(_lastBoundedOffset!.dy, finalPosition.dy, lerpValue)!; + + renderEditable.setFloatingCursor(FloatingCursorDragState.Update, + Offset(lerpX, lerpY), _lastTextPosition!, + resetLerpValue: lerpValue); + } + } + + @pragma('vm:notify-debugger-on-exception') + void _finalizeEditing(TextInputAction action, {required bool shouldUnfocus}) { + // Take any actions necessary now that the user has completed editing. + if (widget.onEditingComplete != null) { + try { + widget.onEditingComplete!(); + } catch (exception, stack) { + FlutterError.reportError(FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'widgets', + context: + ErrorDescription('while calling onEditingComplete for $action'), + )); + } + } else { + // Default behavior if the developer did not provide an + // onEditingComplete callback: Finalize editing and remove focus, or move + // it to the next/previous field, depending on the action. + widget.controller.clearComposing(); + if (shouldUnfocus) { + switch (action) { + case TextInputAction.none: + case TextInputAction.unspecified: + case TextInputAction.done: + case TextInputAction.go: + case TextInputAction.search: + case TextInputAction.send: + case TextInputAction.continueAction: + case TextInputAction.join: + case TextInputAction.route: + case TextInputAction.emergencyCall: + case TextInputAction.newline: + widget.focusNode.unfocus(); + case TextInputAction.next: + widget.focusNode.nextFocus(); + case TextInputAction.previous: + widget.focusNode.previousFocus(); + } + } + } + + final ValueChanged? onSubmitted = widget.onSubmitted; + if (onSubmitted == null) { + return; + } + + // Invoke optional callback with the user's submitted content. + try { + onSubmitted(_value.text); + } catch (exception, stack) { + FlutterError.reportError(FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'widgets', + context: ErrorDescription('while calling onSubmitted for $action'), + )); + } + + // If `shouldUnfocus` is true, the text field should no longer be focused + // after the microtask queue is drained. But in case the developer cancelled + // the focus change in the `onSubmitted` callback by focusing this input + // field again, reset the soft keyboard. + // See https://github.com/flutter/flutter/issues/84240. + // + // `_restartConnectionIfNeeded` creates a new TextInputConnection to replace + // the current one. This on iOS switches to a new input view and on Android + // restarts the input method, and in both cases the soft keyboard will be + // reset. + if (shouldUnfocus) { + _scheduleRestartConnection(); + } + } + + int _batchEditDepth = 0; + + /// Begins a new batch edit, within which new updates made to the text editing + /// value will not be sent to the platform text input plugin. + /// + /// Batch edits nest. When the outermost batch edit finishes, [endBatchEdit] + /// will attempt to send [currentTextEditingValue] to the text input plugin if + /// it detected a change. + void beginBatchEdit() { + _batchEditDepth += 1; + } + + /// Ends the current batch edit started by the last call to [beginBatchEdit], + /// and send [currentTextEditingValue] to the text input plugin if needed. + /// + /// Throws an error in debug mode if this [EditableText] is not in a batch + /// edit. + void endBatchEdit() { + _batchEditDepth -= 1; + assert( + _batchEditDepth >= 0, + 'Unbalanced call to endBatchEdit: beginBatchEdit must be called first.', + ); + _updateRemoteEditingValueIfNeeded(); + } + + void _updateRemoteEditingValueIfNeeded() { + if (_batchEditDepth > 0 || !_hasInputConnection) { + return; + } + final TextEditingValue localValue = _value; + if (localValue == _lastKnownRemoteTextEditingValue) { + return; + } + _textInputConnection!.setEditingState(localValue); + _lastKnownRemoteTextEditingValue = localValue; + } + + TextEditingValue get _value => widget.controller.value; + set _value(TextEditingValue value) { + widget.controller.value = value; + } + + bool get _hasFocus => widget.focusNode.hasFocus; + bool get _isMultiline => widget.maxLines != 1; + + // Finds the closest scroll offset to the current scroll offset that fully + // reveals the given caret rect. If the given rect's main axis extent is too + // large to be fully revealed in `renderEditable`, it will be centered along + // the main axis. + // + // If this is a multiline EditableText (which means the Editable can only + // scroll vertically), the given rect's height will first be extended to match + // `renderEditable.preferredLineHeight`, before the target scroll offset is + // calculated. + RevealedOffset _getOffsetToRevealCaret(Rect rect) { + if (!_scrollController.position.allowImplicitScrolling) { + return RevealedOffset(offset: _scrollController.offset, rect: rect); + } + + final Size editableSize = renderEditable.size; + final double additionalOffset; + final Offset unitOffset; + + if (!_isMultiline) { + additionalOffset = rect.width >= editableSize.width + // Center `rect` if it's oversized. + ? editableSize.width / 2 - rect.center.dx + // Valid additional offsets range from (rect.right - size.width) + // to (rect.left). Pick the closest one if out of range. + : clampDouble(0.0, rect.right - editableSize.width, rect.left); + unitOffset = const Offset(1, 0); + } else { + // The caret is vertically centered within the line. Expand the caret's + // height so that it spans the line because we're going to ensure that the + // entire expanded caret is scrolled into view. + final Rect expandedRect = Rect.fromCenter( + center: rect.center, + width: rect.width, + height: math.max(rect.height, renderEditable.preferredLineHeight), + ); + + additionalOffset = expandedRect.height >= editableSize.height + ? editableSize.height / 2 - expandedRect.center.dy + : clampDouble( + 0.0, expandedRect.bottom - editableSize.height, expandedRect.top); + unitOffset = const Offset(0, 1); + } + + // No overscrolling when encountering tall fonts/scripts that extend past + // the ascent. + final double targetOffset = clampDouble( + additionalOffset + _scrollController.offset, + _scrollController.position.minScrollExtent, + _scrollController.position.maxScrollExtent, + ); + + final double offsetDelta = _scrollController.offset - targetOffset; + return RevealedOffset( + rect: rect.shift(unitOffset * offsetDelta), offset: targetOffset); + } + + /// Whether to send the autofill information to the autofill service. True by + /// default. + bool get _needsAutofill => _effectiveAutofillClient + .textInputConfiguration.autofillConfiguration.enabled; + + // Must be called after layout. + // See https://github.com/flutter/flutter/issues/126312 + void _openInputConnection() { + if (!_shouldCreateInputConnection) { + return; + } + if (!_hasInputConnection) { + final TextEditingValue localValue = _value; + + // When _needsAutofill == true && currentAutofillScope == null, autofill + // is allowed but saving the user input from the text field is + // discouraged. + // + // In case the autofillScope changes from a non-null value to null, or + // _needsAutofill changes to false from true, the platform needs to be + // notified to exclude this field from the autofill context. So we need to + // provide the autofillId. + _textInputConnection = _needsAutofill && currentAutofillScope != null + ? currentAutofillScope! + .attach(this, _effectiveAutofillClient.textInputConfiguration) + : TextInput.attach( + this, _effectiveAutofillClient.textInputConfiguration); + _updateSizeAndTransform(); + _schedulePeriodicPostFrameCallbacks(); + _textInputConnection! + ..setStyle( + fontFamily: _style.fontFamily, + fontSize: _style.fontSize, + fontWeight: _style.fontWeight, + textDirection: _textDirection, + textAlign: widget.textAlign, + ) + ..setEditingState(localValue) + ..show(); + if (_needsAutofill) { + // Request autofill AFTER the size and the transform have been sent to + // the platform text input plugin. + _textInputConnection!.requestAutofill(); + } + _lastKnownRemoteTextEditingValue = localValue; + } else { + _textInputConnection!.show(); + } + } + + void _closeInputConnectionIfNeeded() { + if (_hasInputConnection) { + _textInputConnection!.close(); + _textInputConnection = null; + _lastKnownRemoteTextEditingValue = null; + _scribbleCacheKey = null; + removeTextPlaceholder(); + } + } + + void _openOrCloseInputConnectionIfNeeded() { + if (_hasFocus && widget.focusNode.consumeKeyboardToken()) { + _openInputConnection(); + } else if (!_hasFocus) { + _closeInputConnectionIfNeeded(); + widget.controller.clearComposing(); + } + } + + bool _restartConnectionScheduled = false; + void _scheduleRestartConnection() { + if (_restartConnectionScheduled) { + return; + } + _restartConnectionScheduled = true; + scheduleMicrotask(_restartConnectionIfNeeded); + } + + // Discards the current [TextInputConnection] and establishes a new one. + // + // This method is rarely needed. This is currently used to reset the input + // type when the "submit" text input action is triggered and the developer + // puts the focus back to this input field.. + void _restartConnectionIfNeeded() { + _restartConnectionScheduled = false; + if (!_hasInputConnection || !_shouldCreateInputConnection) { + return; + } + _textInputConnection!.close(); + _textInputConnection = null; + _lastKnownRemoteTextEditingValue = null; + + final AutofillScope? currentAutofillScope = + _needsAutofill ? this.currentAutofillScope : null; + final TextInputConnection newConnection = currentAutofillScope?.attach( + this, textInputConfiguration) ?? + TextInput.attach(this, _effectiveAutofillClient.textInputConfiguration); + _textInputConnection = newConnection; + + newConnection + ..show() + ..setStyle( + fontFamily: _style.fontFamily, + fontSize: _style.fontSize, + fontWeight: _style.fontWeight, + textDirection: _textDirection, + textAlign: widget.textAlign, + ) + ..setEditingState(_value); + _lastKnownRemoteTextEditingValue = _value; + } + + @override + void didChangeInputControl( + TextInputControl? oldControl, TextInputControl? newControl) { + if (_hasFocus && _hasInputConnection) { + oldControl?.hide(); + newControl?.show(); + } + } + + @override + void connectionClosed() { + if (_hasInputConnection) { + _textInputConnection!.connectionClosedReceived(); + _textInputConnection = null; + _lastKnownRemoteTextEditingValue = null; + widget.focusNode.unfocus(); + } + } + + // Indicates that a call to _handleFocusChanged originated within + // EditableText, allowing it to distinguish between internal and external + // focus changes. + bool _nextFocusChangeIsInternal = false; + + // Sets _nextFocusChangeIsInternal to true only until any subsequent focus + // change happens. + void _flagInternalFocus() { + _nextFocusChangeIsInternal = true; + FocusManager.instance.addListener(_unflagInternalFocus); + } + + void _unflagInternalFocus() { + _nextFocusChangeIsInternal = false; + FocusManager.instance.removeListener(_unflagInternalFocus); + } + + /// Express interest in interacting with the keyboard. + /// + /// If this control is already attached to the keyboard, this function will + /// request that the keyboard become visible. Otherwise, this function will + /// ask the focus system that it become focused. If successful in acquiring + /// focus, the control will then attach to the keyboard and request that the + /// keyboard become visible. + void requestKeyboard() { + if (_hasFocus) { + _openInputConnection(); + } else { + _flagInternalFocus(); + widget.focusNode + .requestFocus(); // This eventually calls _openInputConnection also, see _handleFocusChanged. + } + } + + void _updateOrDisposeSelectionOverlayIfNeeded() { + if (_selectionOverlay != null) { + if (_hasFocus) { + _selectionOverlay!.update(_value); + } else { + _selectionOverlay!.dispose(); + _selectionOverlay = null; + } + } + } + + final bool _platformSupportsFadeOnScroll = switch (defaultTargetPlatform) { + TargetPlatform.android || TargetPlatform.iOS => true, + TargetPlatform.fuchsia || + TargetPlatform.linux || + TargetPlatform.macOS || + TargetPlatform.windows => + false, + }; + + bool _isInternalScrollableNotification(BuildContext? notificationContext) { + final ScrollableState? scrollableState = + notificationContext?.findAncestorStateOfType(); + return _scrollableKey.currentContext == scrollableState?.context; + } + + bool _scrollableNotificationIsFromSameSubtree( + BuildContext? notificationContext) { + if (notificationContext == null) { + return false; + } + BuildContext? currentContext = context; + // The notification context of a ScrollNotification points to the RawGestureDetector + // of the Scrollable. We get the ScrollableState associated with this notification + // by looking up the tree. + final ScrollableState? notificationScrollableState = + notificationContext.findAncestorStateOfType(); + if (notificationScrollableState == null) { + return false; + } + while (currentContext != null) { + final ScrollableState? scrollableState = + currentContext.findAncestorStateOfType(); + if (scrollableState == notificationScrollableState) { + return true; + } + currentContext = scrollableState?.context; + } + return false; + } + + void _handleContextMenuOnParentScroll(ScrollNotification notification) { + // Do some preliminary checks to avoid expensive subtree traversal. + if (notification is! ScrollStartNotification && + notification is! ScrollEndNotification) { + return; + } + switch (notification) { + case ScrollStartNotification() when _dataWhenToolbarShowScheduled != null: + case ScrollEndNotification() when _dataWhenToolbarShowScheduled == null: + break; + case ScrollEndNotification() + when _dataWhenToolbarShowScheduled!.value != _value: + _dataWhenToolbarShowScheduled = null; + _disposeScrollNotificationObserver(); + case ScrollNotification(:final BuildContext? context) + when !_isInternalScrollableNotification(context) && + _scrollableNotificationIsFromSameSubtree(context): + _handleContextMenuOnScroll(notification); + } + } + + Rect _calculateDeviceRect() { + final Size screenSize = MediaQuery.sizeOf(context); + final ui.FlutterView view = View.of(context); + final double obscuredVertical = + (view.padding.top + view.padding.bottom + view.viewInsets.bottom) / + view.devicePixelRatio; + final double obscuredHorizontal = + (view.padding.left + view.padding.right) / view.devicePixelRatio; + final Size visibleScreenSize = Size(screenSize.width - obscuredHorizontal, + screenSize.height - obscuredVertical); + return Rect.fromLTWH( + view.padding.left / view.devicePixelRatio, + view.padding.top / view.devicePixelRatio, + visibleScreenSize.width, + visibleScreenSize.height); + } + + bool _showToolbarOnScreenScheduled = false; + void _handleContextMenuOnScroll(ScrollNotification notification) { + if (_webContextMenuEnabled) { + return; + } + if (!_platformSupportsFadeOnScroll) { + _selectionOverlay?.updateForScroll(); + return; + } + // When the scroll begins and the toolbar is visible, hide it + // until scrolling ends. + // + // The selection and renderEditable need to be visible within the current + // viewport for the toolbar to show when scrolling ends. If they are not + // then the toolbar is shown when they are scrolled back into view, unless + // invalidated by a change in TextEditingValue. + if (notification is ScrollStartNotification) { + if (_dataWhenToolbarShowScheduled != null) { + return; + } + final bool toolbarIsVisible = _selectionOverlay != null && + _selectionOverlay!.toolbarIsVisible && + !_selectionOverlay!.spellCheckToolbarIsVisible; + if (!toolbarIsVisible) { + return; + } + final List selectionBoxes = + renderEditable.getBoxesForSelection(_value.selection); + final Rect selectionBounds = _value.selection.isCollapsed || + selectionBoxes.isEmpty + ? renderEditable.getLocalRectForCaret(_value.selection.extent) + : selectionBoxes + .map((TextBox box) => box.toRect()) + .reduce((Rect result, Rect rect) => result.expandToInclude(rect)); + _dataWhenToolbarShowScheduled = + (value: _value, selectionBounds: selectionBounds); + _selectionOverlay?.hideToolbar(); + } else if (notification is ScrollEndNotification) { + if (_dataWhenToolbarShowScheduled == null) { + return; + } + if (_dataWhenToolbarShowScheduled!.value != _value) { + // Value has changed so we should invalidate any toolbar scheduling. + _dataWhenToolbarShowScheduled = null; + _disposeScrollNotificationObserver(); + return; + } + + if (_showToolbarOnScreenScheduled) { + return; + } + _showToolbarOnScreenScheduled = true; + SchedulerBinding.instance.addPostFrameCallback((Duration _) { + _showToolbarOnScreenScheduled = false; + if (!mounted) { + return; + } + final Rect deviceRect = _calculateDeviceRect(); + final bool selectionVisibleInEditable = + renderEditable.selectionStartInViewport.value || + renderEditable.selectionEndInViewport.value; + final Rect selectionBounds = MatrixUtils.transformRect( + renderEditable.getTransformTo(null), + _dataWhenToolbarShowScheduled!.selectionBounds); + final bool selectionOverlapsWithDeviceRect = + !selectionBounds.hasNaN && deviceRect.overlaps(selectionBounds); + + if (selectionVisibleInEditable && + selectionOverlapsWithDeviceRect && + _selectionInViewport( + _dataWhenToolbarShowScheduled!.selectionBounds)) { + showToolbar(); + _dataWhenToolbarShowScheduled = null; + } + }, debugLabel: 'EditableText.scheduleToolbar'); + } + } + + bool _selectionInViewport(Rect selectionBounds) { + RenderAbstractViewport? closestViewport = + RenderAbstractViewport.maybeOf(renderEditable); + while (closestViewport != null) { + final Rect selectionBoundsLocalToViewport = MatrixUtils.transformRect( + renderEditable.getTransformTo(closestViewport), selectionBounds); + if (selectionBoundsLocalToViewport.hasNaN || + closestViewport.paintBounds.hasNaN || + !closestViewport.paintBounds + .overlaps(selectionBoundsLocalToViewport)) { + return false; + } + closestViewport = RenderAbstractViewport.maybeOf(closestViewport.parent); + } + return true; + } + + // zmtzawqlp + _TextSelectionOverlay _createSelectionOverlay() { + // final EditableTextContextMenuBuilder? contextMenuBuilder = + // widget.contextMenuBuilder; + final _TextSelectionOverlay selectionOverlay = _TextSelectionOverlay( + clipboardStatus: clipboardStatus, + context: context, + value: _value, + debugRequiredFor: widget, + toolbarLayerLink: _toolbarLayerLink, + startHandleLayerLink: _startHandleLayerLink, + endHandleLayerLink: _endHandleLayerLink, + renderObject: renderEditable, + selectionControls: widget.selectionControls, + selectionDelegate: this, + dragStartBehavior: widget.dragStartBehavior, + onSelectionHandleTapped: widget.onSelectionHandleTapped, + // zmtzawqlp + // contextMenuBuilder: contextMenuBuilder == null || _webContextMenuEnabled + // ? null + // : (BuildContext context) { + // return contextMenuBuilder( + // context, + // this, + // ); + // }, + magnifierConfiguration: widget.magnifierConfiguration, + ); + + return selectionOverlay; + } + + @pragma('vm:notify-debugger-on-exception') + void _handleSelectionChanged( + TextSelection selection, SelectionChangedCause? cause) { + // We return early if the selection is not valid. This can happen when the + // text of [EditableText] is updated at the same time as the selection is + // changed by a gesture event. + final String text = widget.controller.value.text; + if (text.length < selection.end || text.length < selection.start) { + return; + } + + widget.controller.selection = selection; + + // This will show the keyboard for all selection changes on the + // EditableText except for those triggered by a keyboard input. + // Typically EditableText shouldn't take user keyboard input if + // it's not focused already. If the EditableText is being + // autofilled it shouldn't request focus. + switch (cause) { + case null: + case SelectionChangedCause.doubleTap: + case SelectionChangedCause.drag: + case SelectionChangedCause.forcePress: + case SelectionChangedCause.longPress: + case SelectionChangedCause.scribble: + case SelectionChangedCause.tap: + case SelectionChangedCause.toolbar: + requestKeyboard(); + case SelectionChangedCause.keyboard: + if (_hasFocus) { + requestKeyboard(); + } + } + if (widget.selectionControls == null && widget.contextMenuBuilder == null) { + _selectionOverlay?.dispose(); + _selectionOverlay = null; + } else { + if (_selectionOverlay == null) { + _selectionOverlay = _createSelectionOverlay(); + } else { + _selectionOverlay!.update(_value); + } + _selectionOverlay!.handlesVisible = widget.showSelectionHandles; + _selectionOverlay!.showHandles(); + } + // TODO(chunhtai): we should make sure selection actually changed before + // we call the onSelectionChanged. + // https://github.com/flutter/flutter/issues/76349. + try { + widget.onSelectionChanged?.call(selection, cause); + } catch (exception, stack) { + FlutterError.reportError(FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'widgets', + context: + ErrorDescription('while calling onSelectionChanged for $cause'), + )); + } + + // To keep the cursor from blinking while it moves, restart the timer here. + if (_showBlinkingCursor && _cursorTimer != null) { + _stopCursorBlink(resetCharTicks: false); + _startCursorBlink(); + } + } + + // Animation configuration for scrolling the caret back on screen. + static const Duration _caretAnimationDuration = Duration(milliseconds: 100); + static const Curve _caretAnimationCurve = Curves.fastOutSlowIn; + + bool _showCaretOnScreenScheduled = false; + + void _scheduleShowCaretOnScreen({required bool withAnimation}) { + if (_showCaretOnScreenScheduled) { + return; + } + _showCaretOnScreenScheduled = true; + SchedulerBinding.instance.addPostFrameCallback((Duration _) { + _showCaretOnScreenScheduled = false; + // Since we are in a post frame callback, check currentContext in case + // RenderEditable has been disposed (in which case it will be null). + // zmtzawqlp + final _RenderEditable? renderEditable = + _editableKey.currentContext?.findRenderObject() as _RenderEditable?; + if (renderEditable == null || + !(renderEditable.selection?.isValid ?? false) || + !_scrollController.hasClients) { + return; + } + + final double lineHeight = renderEditable.preferredLineHeight; + + // Enlarge the target rect by scrollPadding to ensure that caret is not + // positioned directly at the edge after scrolling. + double bottomSpacing = widget.scrollPadding.bottom; + if (_selectionOverlay?.selectionControls != null) { + final double handleHeight = _selectionOverlay!.selectionControls! + .getHandleSize(lineHeight) + .height; + final double interactiveHandleHeight = math.max( + handleHeight, + kMinInteractiveDimension, + ); + final Offset anchor = + _selectionOverlay!.selectionControls!.getHandleAnchor( + TextSelectionHandleType.collapsed, + lineHeight, + ); + final double handleCenter = handleHeight / 2 - anchor.dy; + bottomSpacing = math.max( + handleCenter + interactiveHandleHeight / 2, + bottomSpacing, + ); + } + + final EdgeInsets caretPadding = + widget.scrollPadding.copyWith(bottom: bottomSpacing); + + final Rect caretRect = + renderEditable.getLocalRectForCaret(renderEditable.selection!.extent); + final RevealedOffset targetOffset = _getOffsetToRevealCaret(caretRect); + + final Rect rectToReveal; + final TextSelection selection = textEditingValue.selection; + if (selection.isCollapsed) { + rectToReveal = targetOffset.rect; + } else { + final List selectionBoxes = + renderEditable.getBoxesForSelection(selection); + // selectionBoxes may be empty if, for example, the selection does not + // encompass a full character, like if it only contained part of an + // extended grapheme cluster. + if (selectionBoxes.isEmpty) { + rectToReveal = targetOffset.rect; + } else { + rectToReveal = selection.baseOffset < selection.extentOffset + ? selectionBoxes.last.toRect() + : selectionBoxes.first.toRect(); + } + } + + if (withAnimation) { + _scrollController.animateTo( + targetOffset.offset, + duration: _caretAnimationDuration, + curve: _caretAnimationCurve, + ); + renderEditable.showOnScreen( + rect: caretPadding.inflateRect(rectToReveal), + duration: _caretAnimationDuration, + curve: _caretAnimationCurve, + ); + } else { + _scrollController.jumpTo(targetOffset.offset); + renderEditable.showOnScreen( + rect: caretPadding.inflateRect(rectToReveal), + ); + } + }, debugLabel: 'EditableText.showCaret'); + } + + late double _lastBottomViewInset; + + @override + void didChangeMetrics() { + if (!mounted) { + return; + } + final ui.FlutterView view = View.of(context); + if (_lastBottomViewInset != view.viewInsets.bottom) { + SchedulerBinding.instance.addPostFrameCallback((Duration _) { + _selectionOverlay?.updateForScroll(); + }, debugLabel: 'EditableText.updateForScroll'); + if (_lastBottomViewInset < view.viewInsets.bottom) { + // Because the metrics change signal from engine will come here every frame + // (on both iOS and Android). So we don't need to show caret with animation. + _scheduleShowCaretOnScreen(withAnimation: false); + } + } + _lastBottomViewInset = view.viewInsets.bottom; + } + + Future _performSpellCheck(final String text) async { + try { + final Locale? localeForSpellChecking = + widget.locale ?? Localizations.maybeLocaleOf(context); + + assert( + localeForSpellChecking != null, + 'Locale must be specified in widget or Localization widget must be in scope', + ); + + final List? suggestions = await _spellCheckConfiguration + .spellCheckService! + .fetchSpellCheckSuggestions(localeForSpellChecking!, text); + + if (suggestions == null) { + // The request to fetch spell check suggestions was canceled due to ongoing request. + return; + } + + spellCheckResults = SpellCheckResults(text, suggestions); + renderEditable.text = buildTextSpan(); + } catch (exception, stack) { + FlutterError.reportError(FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'widgets', + context: ErrorDescription('while performing spell check'), + )); + } + } + + @pragma('vm:notify-debugger-on-exception') + void _formatAndSetValue(TextEditingValue value, SelectionChangedCause? cause, + {bool userInteraction = false}) { + final TextEditingValue oldValue = _value; + final bool textChanged = oldValue.text != value.text; + final bool textCommitted = + !oldValue.composing.isCollapsed && value.composing.isCollapsed; + final bool selectionChanged = oldValue.selection != value.selection; + + if (textChanged || textCommitted) { + // Only apply input formatters if the text has changed (including uncommitted + // text in the composing region), or when the user committed the composing + // text. + // Gboard is very persistent in restoring the composing region. Applying + // input formatters on composing-region-only changes (except clearing the + // current composing region) is very infinite-loop-prone: the formatters + // will keep trying to modify the composing region while Gboard will keep + // trying to restore the original composing region. + try { + value = widget.inputFormatters?.fold( + value, + (TextEditingValue newValue, TextInputFormatter formatter) => + formatter.formatEditUpdate(_value, newValue), + ) ?? + value; + + if (spellCheckEnabled && + value.text.isNotEmpty && + _value.text != value.text) { + _performSpellCheck(value.text); + } + } catch (exception, stack) { + FlutterError.reportError(FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'widgets', + context: ErrorDescription('while applying input formatters'), + )); + } + } + + final TextSelection oldTextSelection = textEditingValue.selection; + + // Put all optional user callback invocations in a batch edit to prevent + // sending multiple `TextInput.updateEditingValue` messages. + beginBatchEdit(); + _value = value; + // Changes made by the keyboard can sometimes be "out of band" for listening + // components, so always send those events, even if we didn't think it + // changed. Also, the user long pressing should always send a selection change + // as well. + if (selectionChanged || + (userInteraction && + (cause == SelectionChangedCause.longPress || + cause == SelectionChangedCause.keyboard))) { + _handleSelectionChanged(_value.selection, cause); + _bringIntoViewBySelectionState(oldTextSelection, value.selection, cause); + } + final String currentText = _value.text; + if (oldValue.text != currentText) { + try { + widget.onChanged?.call(currentText); + } catch (exception, stack) { + FlutterError.reportError(FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'widgets', + context: ErrorDescription('while calling onChanged'), + )); + } + } + endBatchEdit(); + } + + void _bringIntoViewBySelectionState(TextSelection oldSelection, + TextSelection newSelection, SelectionChangedCause? cause) { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + if (cause == SelectionChangedCause.longPress || + cause == SelectionChangedCause.drag) { + bringIntoView(newSelection.extent); + } + case TargetPlatform.linux: + case TargetPlatform.windows: + case TargetPlatform.fuchsia: + case TargetPlatform.android: + if (cause == SelectionChangedCause.drag) { + if (oldSelection.baseOffset != newSelection.baseOffset) { + bringIntoView(newSelection.base); + } else if (oldSelection.extentOffset != newSelection.extentOffset) { + bringIntoView(newSelection.extent); + } + } + } + } + + void _onCursorColorTick() { + final double effectiveOpacity = math.min( + widget.cursorColor.alpha / 255.0, _cursorBlinkOpacityController.value); + renderEditable.cursorColor = + widget.cursorColor.withOpacity(effectiveOpacity); + _cursorVisibilityNotifier.value = widget.showCursor && + (EditableText.debugDeterministicCursor || + _cursorBlinkOpacityController.value > 0); + } + + bool get _showBlinkingCursor => + _hasFocus && + _value.selection.isCollapsed && + widget.showCursor && + _tickersEnabled && + !renderEditable.floatingCursorOn; + + /// Whether the blinking cursor is actually visible at this precise moment + /// (it's hidden half the time, since it blinks). + @visibleForTesting + bool get cursorCurrentlyVisible => _cursorBlinkOpacityController.value > 0; + + /// The cursor blink interval (the amount of time the cursor is in the "on" + /// state or the "off" state). A complete cursor blink period is twice this + /// value (half on, half off). + @visibleForTesting + Duration get cursorBlinkInterval => _kCursorBlinkHalfPeriod; + + /// The current status of the text selection handles. + + // @visibleForTesting + // zmtzawqlp + // TextSelectionOverlay? get selectionOverlay => _selectionOverlay; + + int _obscureShowCharTicksPending = 0; + int? _obscureLatestCharIndex; + + void _startCursorBlink() { + assert(!(_cursorTimer?.isActive ?? false) || + !(_backingCursorBlinkOpacityController?.isAnimating ?? false)); + if (!widget.showCursor) { + return; + } + if (!_tickersEnabled) { + return; + } + _cursorTimer?.cancel(); + _cursorBlinkOpacityController.value = 1.0; + if (EditableText.debugDeterministicCursor) { + return; + } + if (widget.cursorOpacityAnimates) { + _cursorBlinkOpacityController + .animateWith(_iosBlinkCursorSimulation) + .whenComplete(_onCursorTick); + } else { + _cursorTimer = Timer.periodic(_kCursorBlinkHalfPeriod, (Timer timer) { + _onCursorTick(); + }); + } + } + + void _onCursorTick() { + if (_obscureShowCharTicksPending > 0) { + _obscureShowCharTicksPending = + WidgetsBinding.instance.platformDispatcher.brieflyShowPassword + ? _obscureShowCharTicksPending - 1 + : 0; + if (_obscureShowCharTicksPending == 0) { + setState(() {}); + } + } + + if (widget.cursorOpacityAnimates) { + _cursorTimer?.cancel(); + // Schedule this as an async task to avoid blocking tester.pumpAndSettle + // indefinitely. + _cursorTimer = Timer( + Duration.zero, + () => _cursorBlinkOpacityController + .animateWith(_iosBlinkCursorSimulation) + .whenComplete(_onCursorTick)); + } else { + if (!(_cursorTimer?.isActive ?? false) && _tickersEnabled) { + _cursorTimer = Timer.periodic(_kCursorBlinkHalfPeriod, (Timer timer) { + _onCursorTick(); + }); + } + _cursorBlinkOpacityController.value = + _cursorBlinkOpacityController.value == 0 ? 1 : 0; + } + } + + void _stopCursorBlink({bool resetCharTicks = true}) { + // If the cursor is animating, stop the animation, and we always + // want the cursor to be visible when the floating cursor is enabled. + _cursorBlinkOpacityController.value = + renderEditable.floatingCursorOn ? 1.0 : 0.0; + _cursorTimer?.cancel(); + _cursorTimer = null; + if (resetCharTicks) { + _obscureShowCharTicksPending = 0; + } + } + + void _startOrStopCursorTimerIfNeeded() { + if (!_showBlinkingCursor) { + _stopCursorBlink(); + } else if (_cursorTimer == null) { + _startCursorBlink(); + } + } + + void _didChangeTextEditingValue() { + if (_hasFocus && !_value.selection.isValid) { + // If this field is focused and the selection is invalid, place the cursor at + // the end. Does not rely on _handleFocusChanged because it makes selection + // handles visible on Android. + // Unregister as a listener to the text controller while making the change. + widget.controller.removeListener(_didChangeTextEditingValue); + widget.controller.selection = _adjustedSelectionWhenFocused()!; + widget.controller.addListener(_didChangeTextEditingValue); + } + _updateRemoteEditingValueIfNeeded(); + _startOrStopCursorTimerIfNeeded(); + _updateOrDisposeSelectionOverlayIfNeeded(); + // TODO(abarth): Teach RenderEditable about ValueNotifier + // to avoid this setState(). + setState(() {/* We use widget.controller.value in build(). */}); + _verticalSelectionUpdateAction.stopCurrentVerticalRunIfSelectionChanges(); + } + + void _handleFocusChanged() { + _openOrCloseInputConnectionIfNeeded(); + _startOrStopCursorTimerIfNeeded(); + _updateOrDisposeSelectionOverlayIfNeeded(); + if (_hasFocus) { + // Listen for changing viewInsets, which indicates keyboard showing up. + WidgetsBinding.instance.addObserver(this); + _lastBottomViewInset = View.of(context).viewInsets.bottom; + if (!widget.readOnly) { + _scheduleShowCaretOnScreen(withAnimation: true); + } + final TextSelection? updatedSelection = _adjustedSelectionWhenFocused(); + if (updatedSelection != null) { + _handleSelectionChanged(updatedSelection, null); + } + } else { + WidgetsBinding.instance.removeObserver(this); + setState(() { + _currentPromptRectRange = null; + }); + } + updateKeepAlive(); + } + + TextSelection? _adjustedSelectionWhenFocused() { + TextSelection? selection; + final bool shouldSelectAll = widget.selectionEnabled && + kIsWeb && + !_isMultiline && + !_nextFocusChangeIsInternal; + if (shouldSelectAll) { + // On native web, single line tags select all when receiving + // focus. + selection = TextSelection( + baseOffset: 0, + extentOffset: _value.text.length, + ); + } else if (!_value.selection.isValid) { + // Place cursor at the end if the selection is invalid when we receive focus. + selection = TextSelection.collapsed(offset: _value.text.length); + } + return selection; + } + + void _compositeCallback(Layer layer) { + // The callback can be invoked when the layer is detached. + // The input connection can be closed by the platform in which case this + // widget doesn't rebuild. + if (!renderEditable.attached || !_hasInputConnection) { + return; + } + assert(mounted); + assert((context as Element).debugIsActive); + _updateSizeAndTransform(); + } + + // Must be called after layout. + // See https://github.com/flutter/flutter/issues/126312 + void _updateSizeAndTransform() { + final Size size = renderEditable.size; + final Matrix4 transform = renderEditable.getTransformTo(null); + _textInputConnection!.setEditableSizeAndTransform(size, transform); + } + + void _schedulePeriodicPostFrameCallbacks([Duration? duration]) { + if (!_hasInputConnection) { + return; + } + _updateSelectionRects(); + _updateComposingRectIfNeeded(); + _updateCaretRectIfNeeded(); + SchedulerBinding.instance.addPostFrameCallback( + _schedulePeriodicPostFrameCallbacks, + debugLabel: 'EditableText.postFrameCallbacks'); + } + + _ScribbleCacheKey? _scribbleCacheKey; + + void _updateSelectionRects({bool force = false}) { + if (!widget.scribbleEnabled || + defaultTargetPlatform != TargetPlatform.iOS) { + return; + } + + final ScrollDirection scrollDirection = + _scrollController.position.userScrollDirection; + if (scrollDirection != ScrollDirection.idle) { + return; + } + + final InlineSpan inlineSpan = renderEditable.text!; + double? textScaleFactor; + final TextScaler effectiveTextScaler = switch (( + widget.textScaler, textScaleFactor // widget.textScaleFactor + )) { + (final TextScaler textScaler, _) => textScaler, + (null, final double textScaleFactor) => + TextScaler.linear(textScaleFactor), + (null, null) => MediaQuery.textScalerOf(context), + }; + + final _ScribbleCacheKey newCacheKey = _ScribbleCacheKey( + inlineSpan: inlineSpan, + textAlign: widget.textAlign, + textDirection: _textDirection, + textScaler: effectiveTextScaler, + textHeightBehavior: widget.textHeightBehavior ?? + DefaultTextHeightBehavior.maybeOf(context), + locale: widget.locale, + structStyle: widget.strutStyle, + placeholder: _placeholderLocation, + size: renderEditable.size, + ); + + final RenderComparison comparison = force + ? RenderComparison.layout + : _scribbleCacheKey?.compare(newCacheKey) ?? RenderComparison.layout; + if (comparison.index < RenderComparison.layout.index) { + return; + } + _scribbleCacheKey = newCacheKey; + + final List rects = []; + int graphemeStart = 0; + // Can't use _value.text here: the controller value could change between + // frames. + final String plainText = + inlineSpan.toPlainText(includeSemanticsLabels: false); + final CharacterRange characterRange = CharacterRange(plainText); + while (characterRange.moveNext()) { + final int graphemeEnd = graphemeStart + characterRange.current.length; + final List boxes = renderEditable.getBoxesForSelection( + TextSelection(baseOffset: graphemeStart, extentOffset: graphemeEnd), + ); + + final TextBox? box = boxes.isEmpty ? null : boxes.first; + if (box != null) { + final Rect paintBounds = renderEditable.paintBounds; + // Stop early when characters are already below the bottom edge of the + // RenderEditable, regardless of its clipBehavior. + if (paintBounds.bottom <= box.top) { + break; + } + // Include any TextBox which intersects with the RenderEditable. + if (paintBounds.left <= box.right && + box.left <= paintBounds.right && + paintBounds.top <= box.bottom) { + // At least some part of the letter is visible within the text field. + rects.add(SelectionRect( + position: graphemeStart, + bounds: box.toRect(), + direction: box.direction)); + } + } + graphemeStart = graphemeEnd; + } + _textInputConnection!.setSelectionRects(rects); + } + + // Sends the current composing rect to the embedder's text input plugin. + // + // In cases where the composing rect hasn't been updated in the embedder due + // to the lag of asynchronous messages over the channel, the position of the + // current caret rect is used instead. + // + // See: [_updateCaretRectIfNeeded] + void _updateComposingRectIfNeeded() { + final TextRange composingRange = _value.composing; + assert(mounted); + Rect? composingRect = + renderEditable.getRectForComposingRange(composingRange); + // Send the caret location instead if there's no marked text yet. + if (composingRect == null) { + final int offset = composingRange.isValid ? composingRange.start : 0; + composingRect = + renderEditable.getLocalRectForCaret(TextPosition(offset: offset)); + } + _textInputConnection!.setComposingRect(composingRect); + } + + // Sends the current caret rect to the embedder's text input plugin. + // + // The position of the caret rect is updated periodically such that if the + // user initiates composing input, the current cursor rect can be used for + // the first character until the composing rect can be sent. + // + // On selection changes, the start of the selection is used. This ensures + // that regardless of the direction the selection was created, the cursor is + // set to the position where next text input occurs. This position is used to + // position the IME's candidate selection menu. + // + // See: [_updateComposingRectIfNeeded] + void _updateCaretRectIfNeeded() { + final TextSelection? selection = renderEditable.selection; + if (selection == null || !selection.isValid) { + return; + } + final TextPosition currentTextPosition = + TextPosition(offset: selection.start); + final Rect caretRect = + renderEditable.getLocalRectForCaret(currentTextPosition); + _textInputConnection!.setCaretRect(caretRect); + } + + TextDirection get _textDirection => + widget.textDirection ?? Directionality.of(context); + + /// The renderer for this widget's descendant. + /// + /// This property is typically used to notify the renderer of input gestures + /// when [RenderEditable.ignorePointer] is true. + /// zmtzawqlp + late final _RenderEditable renderEditable = + _editableKey.currentContext!.findRenderObject()! as _RenderEditable; + + @override + TextEditingValue get textEditingValue => _value; + + double get _devicePixelRatio => MediaQuery.devicePixelRatioOf(context); + + @override + void userUpdateTextEditingValue( + TextEditingValue value, SelectionChangedCause? cause) { + // Compare the current TextEditingValue with the pre-format new + // TextEditingValue value, in case the formatter would reject the change. + final bool shouldShowCaret = + widget.readOnly ? _value.selection != value.selection : _value != value; + if (shouldShowCaret) { + _scheduleShowCaretOnScreen(withAnimation: true); + } + + // Even if the value doesn't change, it may be necessary to focus and build + // the selection overlay. For example, this happens when right clicking an + // unfocused field that previously had a selection in the same spot. + if (value == textEditingValue) { + if (!widget.focusNode.hasFocus) { + _flagInternalFocus(); + widget.focusNode.requestFocus(); + _selectionOverlay ??= _createSelectionOverlay(); + } + return; + } + + _formatAndSetValue(value, cause, userInteraction: true); + } + + @override + void bringIntoView(TextPosition position) { + final Rect localRect = renderEditable.getLocalRectForCaret(position); + final RevealedOffset targetOffset = _getOffsetToRevealCaret(localRect); + + _scrollController.jumpTo(targetOffset.offset); + renderEditable.showOnScreen(rect: targetOffset.rect); + } + + /// Shows the selection toolbar at the location of the current cursor. + /// + /// Returns `false` if a toolbar couldn't be shown, such as when the toolbar + /// is already shown, or when no text selection currently exists. + @override + bool showToolbar() { + // Web is using native dom elements to enable clipboard functionality of the + // context menu: copy, paste, select, cut. It might also provide additional + // functionality depending on the browser (such as translate). Due to this, + // we should not show a Flutter toolbar for the editable text elements + // unless the browser's context menu is explicitly disabled. + if (_webContextMenuEnabled) { + return false; + } + + if (_selectionOverlay == null) { + return false; + } + _liveTextInputStatus?.update(); + clipboardStatus.update(); + _selectionOverlay!.showToolbar(); + // Listen to parent scroll events when the toolbar is visible so it can be + // hidden during a scroll on supported platforms. + if (_platformSupportsFadeOnScroll) { + _listeningToScrollNotificationObserver = true; + _scrollNotificationObserver + ?.removeListener(_handleContextMenuOnParentScroll); + _scrollNotificationObserver = ScrollNotificationObserver.maybeOf(context); + _scrollNotificationObserver + ?.addListener(_handleContextMenuOnParentScroll); + } + return true; + } + + @override + void hideToolbar([bool hideHandles = true]) { + // Stop listening to parent scroll events when toolbar is hidden. + _disposeScrollNotificationObserver(); + if (hideHandles) { + // Hide the handles and the toolbar. + _selectionOverlay?.hide(); + } else if (_selectionOverlay?.toolbarIsVisible ?? false) { + // Hide only the toolbar but not the handles. + _selectionOverlay?.hideToolbar(); + } + } + + /// Toggles the visibility of the toolbar. + void toggleToolbar([bool hideHandles = true]) { + final _TextSelectionOverlay selectionOverlay = + _selectionOverlay ??= _createSelectionOverlay(); + if (selectionOverlay.toolbarIsVisible) { + hideToolbar(hideHandles); + } else { + showToolbar(); + } + } + + /// Shows toolbar with spell check suggestions of misspelled words that are + /// available for click-and-replace. + bool showSpellCheckSuggestionsToolbar() { + // Spell check suggestions toolbars are intended to be shown on non-web + // platforms. Additionally, the Cupertino style toolbar can't be drawn on + // the web with the HTML renderer due to + // https://github.com/flutter/flutter/issues/123560. + if (!spellCheckEnabled || + _webContextMenuEnabled || + widget.readOnly || + _selectionOverlay == null || + !_spellCheckResultsReceived || + findSuggestionSpanAtCursorIndex( + textEditingValue.selection.extentOffset) == + null) { + // Only attempt to show the spell check suggestions toolbar if there + // is a toolbar specified and spell check suggestions available to show. + return false; + } + + assert( + _spellCheckConfiguration.spellCheckSuggestionsToolbarBuilder != null, + 'spellCheckSuggestionsToolbarBuilder must be defined in ' + 'SpellCheckConfiguration to show a toolbar with spell check ' + 'suggestions', + ); + // zmtzawqlp + // _selectionOverlay! + // .showSpellCheckSuggestionsToolbar( + // (BuildContext context) { + // return _spellCheckConfiguration + // .spellCheckSuggestionsToolbarBuilder!( + // context, + // this, + // ); + // }, + // ); + return true; + } + + /// Shows the magnifier at the position given by `positionToShow`, + /// if there is no magnifier visible. + /// + /// Updates the magnifier to the position given by `positionToShow`, + /// if there is a magnifier visible. + /// + /// Does nothing if a magnifier couldn't be shown, such as when the selection + /// overlay does not currently exist. + void showMagnifier(Offset positionToShow) { + if (_selectionOverlay == null) { + return; + } + + if (_selectionOverlay!.magnifierIsVisible) { + _selectionOverlay!.updateMagnifier(positionToShow); + } else { + _selectionOverlay!.showMagnifier(positionToShow); + } + } + + /// Hides the magnifier if it is visible. + void hideMagnifier() { + if (_selectionOverlay == null) { + return; + } + + if (_selectionOverlay!.magnifierIsVisible) { + _selectionOverlay!.hideMagnifier(); + } + } + + // Tracks the location a [_ScribblePlaceholder] should be rendered in the + // text. + // + // A value of -1 indicates there should be no placeholder, otherwise the + // value should be between 0 and the length of the text, inclusive. + int _placeholderLocation = -1; + + @override + void insertTextPlaceholder(Size size) { + if (!widget.scribbleEnabled) { + return; + } + + if (!widget.controller.selection.isValid) { + return; + } + + setState(() { + _placeholderLocation = + _value.text.length - widget.controller.selection.end; + }); + } + + @override + void removeTextPlaceholder() { + if (!widget.scribbleEnabled || _placeholderLocation == -1) { + return; + } + + setState(() { + _placeholderLocation = -1; + }); + } + + @override + void performSelector(String selectorName) { + final Intent? intent = intentForMacOSSelector(selectorName); + + if (intent != null) { + final BuildContext? primaryContext = primaryFocus?.context; + if (primaryContext != null) { + Actions.invoke(primaryContext, intent); + } + } + } + + @override + String get autofillId => 'EditableText-$hashCode'; + + int? _viewId; + + @override + TextInputConfiguration get textInputConfiguration { + final List? autofillHints = + widget.autofillHints?.toList(growable: false); + final AutofillConfiguration autofillConfiguration = autofillHints != null + ? AutofillConfiguration( + uniqueIdentifier: autofillId, + autofillHints: autofillHints, + currentEditingValue: currentTextEditingValue, + ) + : AutofillConfiguration.disabled; + + _viewId = View.of(context).viewId; + return TextInputConfiguration( + viewId: _viewId, + inputType: widget.keyboardType, + readOnly: widget.readOnly, + obscureText: widget.obscureText, + autocorrect: widget.autocorrect, + smartDashesType: widget.smartDashesType, + smartQuotesType: widget.smartQuotesType, + enableSuggestions: widget.enableSuggestions, + enableInteractiveSelection: widget._userSelectionEnabled, + inputAction: widget.textInputAction ?? + (widget.keyboardType == TextInputType.multiline + ? TextInputAction.newline + : TextInputAction.done), + textCapitalization: widget.textCapitalization, + keyboardAppearance: widget.keyboardAppearance, + autofillConfiguration: autofillConfiguration, + enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, + allowedMimeTypes: widget.contentInsertionConfiguration == null + ? const [] + : widget.contentInsertionConfiguration!.allowedMimeTypes, + ); + } + + @override + void autofill(TextEditingValue value) => updateEditingValue(value); + + // null if no promptRect should be shown. + TextRange? _currentPromptRectRange; + + @override + void showAutocorrectionPromptRect(int start, int end) { + setState(() { + _currentPromptRectRange = TextRange(start: start, end: end); + }); + } + + VoidCallback? _semanticsOnCopy(TextSelectionControls? controls) { + return widget.selectionEnabled && + _hasFocus && + (widget.selectionControls is TextSelectionHandleControls + ? copyEnabled + : copyEnabled && + (widget.selectionControls?.canCopy(this) ?? false)) + ? () { + controls?.handleCopy(this); + copySelection(SelectionChangedCause.toolbar); + } + : null; + } + + VoidCallback? _semanticsOnCut(TextSelectionControls? controls) { + return widget.selectionEnabled && + _hasFocus && + (widget.selectionControls is TextSelectionHandleControls + ? cutEnabled + : cutEnabled && + (widget.selectionControls?.canCut(this) ?? false)) + ? () { + controls?.handleCut(this); + cutSelection(SelectionChangedCause.toolbar); + } + : null; + } + + VoidCallback? _semanticsOnPaste(TextSelectionControls? controls) { + return widget.selectionEnabled && + _hasFocus && + (widget.selectionControls is TextSelectionHandleControls + ? pasteEnabled + : pasteEnabled && + (widget.selectionControls?.canPaste(this) ?? false)) && + (clipboardStatus.value == ClipboardStatus.pasteable) + ? () { + controls?.handlePaste(this); + pasteText(SelectionChangedCause.toolbar); + } + : null; + } + + // Returns the closest boundary location to `extent` but not including `extent` + // itself (unless already at the start/end of the text), in the direction + // specified by `forward`. + TextPosition _moveBeyondTextBoundary( + TextPosition extent, bool forward, TextBoundary textBoundary) { + assert(extent.offset >= 0); + final int newOffset = forward + ? textBoundary.getTrailingTextBoundaryAt(extent.offset) ?? + _value.text.length + // if x is a boundary defined by `textBoundary`, most textBoundaries (except + // LineBreaker) guarantees `x == textBoundary.getLeadingTextBoundaryAt(x)`. + // Use x - 1 here to make sure we don't get stuck at the fixed point x. + : textBoundary.getLeadingTextBoundaryAt(extent.offset - 1) ?? 0; + return TextPosition(offset: newOffset); + } + + // Returns the closest boundary location to `extent`, including `extent` + // itself, in the direction specified by `forward`. + // + // This method returns a fixed point of itself: applying `_toTextBoundary` + // again on the returned TextPosition gives the same TextPosition. It's used + // exclusively for handling line boundaries, since performing "move to line + // start" more than once usually doesn't move you to the previous line. + TextPosition _moveToTextBoundary( + TextPosition extent, bool forward, TextBoundary textBoundary) { + assert(extent.offset >= 0); + final int caretOffset; + switch (extent.affinity) { + case TextAffinity.upstream: + if (extent.offset < 1 && !forward) { + assert(extent.offset == 0); + return const TextPosition(offset: 0); + } + // When the text affinity is upstream, the caret is associated with the + // grapheme before the code unit at `extent.offset`. + // TODO(LongCatIsLooong): don't assume extent.offset is at a grapheme + // boundary, and do this instead: + // final int graphemeStart = CharacterRange.at(string, extent.offset).stringBeforeLength - 1; + caretOffset = math.max(0, extent.offset - 1); + case TextAffinity.downstream: + caretOffset = extent.offset; + } + // The line boundary range does not include some control characters + // (most notably, Line Feed), in which case there's + // `x ∉ getTextBoundaryAt(x)`. In case `caretOffset` points to one such + // control character, we define that these control characters themselves are + // still part of the previous line, but also exclude them from the + // line boundary range since they're non-printing. IOW, no additional + // processing needed since the LineBoundary class does exactly that. + return forward + ? TextPosition( + offset: textBoundary.getTrailingTextBoundaryAt(caretOffset) ?? + _value.text.length, + affinity: TextAffinity.upstream) + : TextPosition( + offset: textBoundary.getLeadingTextBoundaryAt(caretOffset) ?? 0); + } + + // --------------------------- Text Editing Actions --------------------------- + + TextBoundary _characterBoundary() => widget.obscureText + ? _CodePointBoundary(_value.text) + : CharacterBoundary(_value.text); + TextBoundary _nextWordBoundary() => widget.obscureText + ? _documentBoundary() + : renderEditable.wordBoundaries.moveByWordBoundary; + TextBoundary _linebreak() => + widget.obscureText ? _documentBoundary() : LineBoundary(renderEditable); + TextBoundary _paragraphBoundary() => ParagraphBoundary(_value.text); + TextBoundary _documentBoundary() => DocumentBoundary(_value.text); + + Action _makeOverridable(Action defaultAction) { + return Action.overridable( + context: context, defaultAction: defaultAction); + } + + /// Transpose the characters immediately before and after the current + /// collapsed selection. + /// + /// When the cursor is at the end of the text, transposes the last two + /// characters, if they exist. + /// + /// When the cursor is at the start of the text, does nothing. + void _transposeCharacters(TransposeCharactersIntent intent) { + if (_value.text.characters.length <= 1 || + !_value.selection.isCollapsed || + _value.selection.baseOffset == 0) { + return; + } + + final String text = _value.text; + final TextSelection selection = _value.selection; + final bool atEnd = selection.baseOffset == text.length; + final CharacterRange transposing = + CharacterRange.at(text, selection.baseOffset); + if (atEnd) { + transposing.moveBack(2); + } else { + transposing + ..moveBack() + ..expandNext(); + } + assert(transposing.currentCharacters.length == 2); + + userUpdateTextEditingValue( + TextEditingValue( + text: transposing.stringBefore + + transposing.currentCharacters.last + + transposing.currentCharacters.first + + transposing.stringAfter, + selection: TextSelection.collapsed( + offset: transposing.stringBeforeLength + transposing.current.length, + ), + ), + SelectionChangedCause.keyboard, + ); + } + + late final Action _transposeCharactersAction = + CallbackAction(onInvoke: _transposeCharacters); + + void _replaceText(ReplaceTextIntent intent) { + final TextEditingValue oldValue = _value; + final TextEditingValue newValue = intent.currentTextEditingValue.replaced( + intent.replacementRange, + intent.replacementText, + ); + userUpdateTextEditingValue(newValue, intent.cause); + + // If there's no change in text and selection (e.g. when selecting and + // pasting identical text), the widget won't be rebuilt on value update. + // Handle this by calling _didChangeTextEditingValue() so caret and scroll + // updates can happen. + if (newValue == oldValue) { + _didChangeTextEditingValue(); + } + } + + late final Action _replaceTextAction = + CallbackAction(onInvoke: _replaceText); + + // Scrolls either to the beginning or end of the document depending on the + // intent's `forward` parameter. + void _scrollToDocumentBoundary(ScrollToDocumentBoundaryIntent intent) { + if (intent.forward) { + bringIntoView(TextPosition(offset: _value.text.length)); + } else { + bringIntoView(const TextPosition(offset: 0)); + } + } + + /// Handles [ScrollIntent] by scrolling the [Scrollable] inside of + /// [EditableText]. + void _scroll(ScrollIntent intent) { + if (intent.type != ScrollIncrementType.page) { + return; + } + + final ScrollPosition position = _scrollController.position; + if (widget.maxLines == 1) { + _scrollController.jumpTo(position.maxScrollExtent); + return; + } + + // If the field isn't scrollable, do nothing. For example, when the lines of + // text is less than maxLines, the field has nothing to scroll. + if (position.maxScrollExtent == 0.0 && position.minScrollExtent == 0.0) { + return; + } + + final ScrollableState? state = + _scrollableKey.currentState as ScrollableState?; + final double increment = + ScrollAction.getDirectionalIncrement(state!, intent); + final double destination = clampDouble( + position.pixels + increment, + position.minScrollExtent, + position.maxScrollExtent, + ); + if (destination == position.pixels) { + return; + } + _scrollController.jumpTo(destination); + } + + void _updateSelection(UpdateSelectionIntent intent) { + assert( + intent.newSelection.start <= intent.currentTextEditingValue.text.length, + 'invalid selection: ${intent.newSelection}: it must not exceed the current text length ${intent.currentTextEditingValue.text.length}', + ); + assert( + intent.newSelection.end <= intent.currentTextEditingValue.text.length, + 'invalid selection: ${intent.newSelection}: it must not exceed the current text length ${intent.currentTextEditingValue.text.length}', + ); + + bringIntoView(intent.newSelection.extent); + userUpdateTextEditingValue( + intent.currentTextEditingValue.copyWith(selection: intent.newSelection), + intent.cause, + ); + } + + late final Action _updateSelectionAction = + CallbackAction(onInvoke: _updateSelection); + + late final _UpdateTextSelectionVerticallyAction< + DirectionalCaretMovementIntent> _verticalSelectionUpdateAction = + _UpdateTextSelectionVerticallyAction( + this); + + Object? _hideToolbarIfVisible(DismissIntent intent) { + if (_selectionOverlay?.toolbarIsVisible ?? false) { + hideToolbar(false); + return null; + } + return Actions.invoke(context, intent); + } + + /// The default behavior used if [onTapOutside] is null. + /// + /// The `event` argument is the [PointerDownEvent] that caused the notification. + void _defaultOnTapOutside(PointerDownEvent event) { + /// The focus dropping behavior is only present on desktop platforms + /// and mobile browsers. + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.iOS: + case TargetPlatform.fuchsia: + // On mobile platforms, we don't unfocus on touch events unless they're + // in the web browser, but we do unfocus for all other kinds of events. + switch (event.kind) { + case ui.PointerDeviceKind.touch: + if (kIsWeb) { + widget.focusNode.unfocus(); + } + case ui.PointerDeviceKind.mouse: + case ui.PointerDeviceKind.stylus: + case ui.PointerDeviceKind.invertedStylus: + case ui.PointerDeviceKind.unknown: + widget.focusNode.unfocus(); + case ui.PointerDeviceKind.trackpad: + throw UnimplementedError( + 'Unexpected pointer down event for trackpad'); + } + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + widget.focusNode.unfocus(); + } + } + + late final Map> _actions = >{ + DoNothingAndStopPropagationTextIntent: DoNothingAction(consumesKey: false), + ReplaceTextIntent: _replaceTextAction, + UpdateSelectionIntent: _updateSelectionAction, + DirectionalFocusIntent: DirectionalFocusAction.forTextField(), + DismissIntent: + CallbackAction(onInvoke: _hideToolbarIfVisible), + + // Delete + DeleteCharacterIntent: _makeOverridable( + _DeleteTextAction( + this, _characterBoundary, _moveBeyondTextBoundary)), + DeleteToNextWordBoundaryIntent: _makeOverridable( + _DeleteTextAction( + this, _nextWordBoundary, _moveBeyondTextBoundary)), + DeleteToLineBreakIntent: _makeOverridable( + _DeleteTextAction( + this, _linebreak, _moveToTextBoundary)), + + // Extend/Move Selection + ExtendSelectionByCharacterIntent: _makeOverridable( + _UpdateTextSelectionAction( + this, _characterBoundary, _moveBeyondTextBoundary, + ignoreNonCollapsedSelection: false)), + ExtendSelectionToNextWordBoundaryIntent: _makeOverridable( + _UpdateTextSelectionAction( + this, _nextWordBoundary, _moveBeyondTextBoundary, + ignoreNonCollapsedSelection: true)), + ExtendSelectionToNextParagraphBoundaryIntent: _makeOverridable( + _UpdateTextSelectionAction< + ExtendSelectionToNextParagraphBoundaryIntent>( + this, _paragraphBoundary, _moveBeyondTextBoundary, + ignoreNonCollapsedSelection: true)), + ExtendSelectionToLineBreakIntent: _makeOverridable( + _UpdateTextSelectionAction( + this, _linebreak, _moveToTextBoundary, + ignoreNonCollapsedSelection: true)), + ExtendSelectionVerticallyToAdjacentLineIntent: + _makeOverridable(_verticalSelectionUpdateAction), + ExtendSelectionVerticallyToAdjacentPageIntent: + _makeOverridable(_verticalSelectionUpdateAction), + ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent: + _makeOverridable(_UpdateTextSelectionAction< + ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent>( + this, _paragraphBoundary, _moveBeyondTextBoundary, + ignoreNonCollapsedSelection: true)), + ExtendSelectionToDocumentBoundaryIntent: _makeOverridable( + _UpdateTextSelectionAction( + this, _documentBoundary, _moveBeyondTextBoundary, + ignoreNonCollapsedSelection: true)), + ExtendSelectionToNextWordBoundaryOrCaretLocationIntent: _makeOverridable( + _UpdateTextSelectionAction< + ExtendSelectionToNextWordBoundaryOrCaretLocationIntent>( + this, _nextWordBoundary, _moveBeyondTextBoundary, + ignoreNonCollapsedSelection: true)), + ScrollToDocumentBoundaryIntent: _makeOverridable( + CallbackAction( + onInvoke: _scrollToDocumentBoundary)), + ScrollIntent: CallbackAction(onInvoke: _scroll), + + // Expand Selection + ExpandSelectionToLineBreakIntent: _makeOverridable( + _UpdateTextSelectionAction( + this, _linebreak, _moveToTextBoundary, + ignoreNonCollapsedSelection: true, isExpand: true)), + ExpandSelectionToDocumentBoundaryIntent: _makeOverridable( + _UpdateTextSelectionAction( + this, _documentBoundary, _moveToTextBoundary, + ignoreNonCollapsedSelection: true, + isExpand: true, + extentAtIndex: true)), + + // Copy Paste + SelectAllTextIntent: _makeOverridable(_SelectAllAction(this)), + CopySelectionTextIntent: _makeOverridable(_CopySelectionAction(this)), + PasteTextIntent: _makeOverridable(CallbackAction( + onInvoke: (PasteTextIntent intent) => pasteText(intent.cause))), + + TransposeCharactersIntent: _makeOverridable(_transposeCharactersAction), + }; + + // zmtzawqlp + // @override + // Widget build(BuildContext context) { + // assert(debugCheckHasMediaQuery(context)); + // super.build(context); // See AutomaticKeepAliveClientMixin. + + // final TextSelectionControls? controls = widget.selectionControls; + // final TextScaler effectiveTextScaler = + // switch ((widget.textScaler, widget.textScaleFactor)) { + // (final TextScaler textScaler, _) => textScaler, + // (null, final double textScaleFactor) => + // TextScaler.linear(textScaleFactor), + // (null, null) => MediaQuery.textScalerOf(context), + // }; + + // return _CompositionCallback( + // compositeCallback: _compositeCallback, + // enabled: _hasInputConnection, + // child: TextFieldTapRegion( + // groupId: widget.groupId, + // onTapOutside: + // _hasFocus ? widget.onTapOutside ?? _defaultOnTapOutside : null, + // debugLabel: kReleaseMode ? null : 'EditableText', + // child: MouseRegion( + // cursor: widget.mouseCursor ?? SystemMouseCursors.text, + // child: Actions( + // actions: _actions, + // child: UndoHistory( + // value: widget.controller, + // onTriggered: (TextEditingValue value) { + // userUpdateTextEditingValue( + // value, SelectionChangedCause.keyboard); + // }, + // shouldChangeUndoStack: + // (TextEditingValue? oldValue, TextEditingValue newValue) { + // if (!newValue.selection.isValid) { + // return false; + // } + + // if (oldValue == null) { + // return true; + // } + + // switch (defaultTargetPlatform) { + // case TargetPlatform.iOS: + // case TargetPlatform.macOS: + // case TargetPlatform.fuchsia: + // case TargetPlatform.linux: + // case TargetPlatform.windows: + // // Composing text is not counted in history coalescing. + // if (!widget.controller.value.composing.isCollapsed) { + // return false; + // } + // case TargetPlatform.android: + // // Gboard on Android puts non-CJK words in composing regions. Coalesce + // // composing text in order to allow the saving of partial words in that + // // case. + // break; + // } + + // return oldValue.text != newValue.text || + // oldValue.composing != newValue.composing; + // }, + // undoStackModifier: (TextEditingValue value) { + // // On Android we should discard the composing region when pushing + // // a new entry to the undo stack. This prevents the TextInputPlugin + // // from restarting the input on every undo/redo when the composing + // // region is changed by the framework. + // return defaultTargetPlatform == TargetPlatform.android + // ? value.copyWith(composing: TextRange.empty) + // : value; + // }, + // focusNode: widget.focusNode, + // controller: widget.undoController, + // child: Focus( + // focusNode: widget.focusNode, + // includeSemantics: false, + // debugLabel: kReleaseMode ? null : 'EditableText', + // child: NotificationListener( + // onNotification: (ScrollNotification notification) { + // _handleContextMenuOnScroll(notification); + // _scribbleCacheKey = null; + // return false; + // }, + // child: Scrollable( + // key: _scrollableKey, + // excludeFromSemantics: true, + // axisDirection: + // _isMultiline ? AxisDirection.down : AxisDirection.right, + // controller: _scrollController, + // physics: widget.scrollPhysics, + // dragStartBehavior: widget.dragStartBehavior, + // restorationId: widget.restorationId, + // // If a ScrollBehavior is not provided, only apply scrollbars when + // // multiline. The overscroll indicator should not be applied in + // // either case, glowing or stretching. + // scrollBehavior: widget.scrollBehavior ?? + // ScrollConfiguration.of(context).copyWith( + // scrollbars: _isMultiline, + // overscroll: false, + // ), + // viewportBuilder: + // (BuildContext context, ViewportOffset offset) { + // return CompositedTransformTarget( + // link: _toolbarLayerLink, + // child: Semantics( + // onCopy: _semanticsOnCopy(controls), + // onCut: _semanticsOnCut(controls), + // onPaste: _semanticsOnPaste(controls), + // child: _ScribbleFocusable( + // focusNode: widget.focusNode, + // editableKey: _editableKey, + // enabled: widget.scribbleEnabled, + // updateSelectionRects: () { + // _openInputConnection(); + // _updateSelectionRects(force: true); + // }, + // child: SizeChangedLayoutNotifier( + // child: _Editable( + // key: _editableKey, + // startHandleLayerLink: _startHandleLayerLink, + // endHandleLayerLink: _endHandleLayerLink, + // inlineSpan: buildTextSpan(), + // value: _value, + // cursorColor: _cursorColor, + // backgroundCursorColor: + // widget.backgroundCursorColor, + // showCursor: _cursorVisibilityNotifier, + // forceLine: widget.forceLine, + // readOnly: widget.readOnly, + // hasFocus: _hasFocus, + // maxLines: widget.maxLines, + // minLines: widget.minLines, + // expands: widget.expands, + // strutStyle: widget.strutStyle, + // selectionColor: _selectionOverlay + // ?.spellCheckToolbarIsVisible ?? + // false + // ? _spellCheckConfiguration + // .misspelledSelectionColor ?? + // widget.selectionColor + // : widget.selectionColor, + // textScaler: effectiveTextScaler, + // textAlign: widget.textAlign, + // textDirection: _textDirection, + // locale: widget.locale, + // textHeightBehavior: widget.textHeightBehavior ?? + // DefaultTextHeightBehavior.maybeOf(context), + // textWidthBasis: widget.textWidthBasis, + // obscuringCharacter: widget.obscuringCharacter, + // obscureText: widget.obscureText, + // offset: offset, + // rendererIgnoresPointer: + // widget.rendererIgnoresPointer, + // cursorWidth: widget.cursorWidth, + // cursorHeight: widget.cursorHeight, + // cursorRadius: widget.cursorRadius, + // cursorOffset: + // widget.cursorOffset ?? Offset.zero, + // selectionHeightStyle: + // widget.selectionHeightStyle, + // selectionWidthStyle: widget.selectionWidthStyle, + // paintCursorAboveText: + // widget.paintCursorAboveText, + // enableInteractiveSelection: + // widget._userSelectionEnabled, + // textSelectionDelegate: this, + // devicePixelRatio: _devicePixelRatio, + // promptRectRange: _currentPromptRectRange, + // promptRectColor: + // widget.autocorrectionTextRectColor, + // clipBehavior: widget.clipBehavior, + // ), + // ), + // ), + // ), + // ); + // }, + // ), + // ), + // ), + // ), + // ), + // ), + // ), + // ); + // } + + /// Builds [TextSpan] from current editing value. + /// + /// By default makes text in composing range appear as underlined. + /// Descendants can override this method to customize appearance of text. + TextSpan buildTextSpan() { + if (widget.obscureText) { + String text = _value.text; + text = widget.obscuringCharacter * text.length; + // Reveal the latest character in an obscured field only on mobile. + // Newer versions of iOS (iOS 15+) no longer reveal the most recently + // entered character. + const Set mobilePlatforms = { + TargetPlatform.android, + TargetPlatform.fuchsia, + }; + final bool brieflyShowPassword = + WidgetsBinding.instance.platformDispatcher.brieflyShowPassword && + mobilePlatforms.contains(defaultTargetPlatform); + if (brieflyShowPassword) { + final int? o = + _obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null; + if (o != null && o >= 0 && o < text.length) { + text = text.replaceRange(o, o + 1, _value.text.substring(o, o + 1)); + } + } + return TextSpan(style: _style, text: text); + } + if (_placeholderLocation >= 0 && + _placeholderLocation <= _value.text.length) { + final List<_ScribblePlaceholder> placeholders = <_ScribblePlaceholder>[]; + final int placeholderLocation = _value.text.length - _placeholderLocation; + if (_isMultiline) { + // The zero size placeholder here allows the line to break and keep the caret on the first line. + placeholders.add(const _ScribblePlaceholder( + child: SizedBox.shrink(), size: Size.zero)); + placeholders.add(_ScribblePlaceholder( + child: const SizedBox.shrink(), + size: Size(renderEditable.size.width, 0.0))); + } else { + placeholders.add(const _ScribblePlaceholder( + child: SizedBox.shrink(), size: Size(100.0, 0.0))); + } + return TextSpan( + style: _style, + children: [ + TextSpan(text: _value.text.substring(0, placeholderLocation)), + ...placeholders, + TextSpan(text: _value.text.substring(placeholderLocation)), + ], + ); + } + final bool withComposing = !widget.readOnly && _hasFocus; + if (_spellCheckResultsReceived) { + // If the composing range is out of range for the current text, ignore it to + // preserve the tree integrity, otherwise in release mode a RangeError will + // be thrown and this EditableText will be built with a broken subtree. + assert(!_value.composing.isValid || + !withComposing || + _value.isComposingRangeValid); + + final bool composingRegionOutOfRange = + !_value.isComposingRangeValid || !withComposing; + + return buildTextSpanWithSpellCheckSuggestions( + _value, + composingRegionOutOfRange, + _style, + _spellCheckConfiguration.misspelledTextStyle!, + spellCheckResults!, + ); + } + + // Read only mode should not paint text composing. + return widget.controller.buildTextSpan( + context: context, + style: _style, + withComposing: withComposing, + ); + } +} + +class _Editable extends MultiChildRenderObjectWidget { + _Editable({ + super.key, + required this.inlineSpan, + required this.value, + required this.startHandleLayerLink, + required this.endHandleLayerLink, + this.cursorColor, + this.backgroundCursorColor, + required this.showCursor, + required this.forceLine, + required this.readOnly, + this.textHeightBehavior, + required this.textWidthBasis, + required this.hasFocus, + required this.maxLines, + this.minLines, + required this.expands, + this.strutStyle, + this.selectionColor, + required this.textScaler, + required this.textAlign, + required this.textDirection, + this.locale, + required this.obscuringCharacter, + required this.obscureText, + required this.offset, + this.rendererIgnoresPointer = false, + required this.cursorWidth, + this.cursorHeight, + this.cursorRadius, + required this.cursorOffset, + required this.paintCursorAboveText, + this.selectionHeightStyle = ui.BoxHeightStyle.tight, + this.selectionWidthStyle = ui.BoxWidthStyle.tight, + this.enableInteractiveSelection = true, + required this.textSelectionDelegate, + required this.devicePixelRatio, + this.promptRectRange, + this.promptRectColor, + required this.clipBehavior, + }) : super( + children: WidgetSpan.extractFromInlineSpan(inlineSpan, textScaler)); + + final InlineSpan inlineSpan; + final TextEditingValue value; + final Color? cursorColor; + final LayerLink startHandleLayerLink; + final LayerLink endHandleLayerLink; + final Color? backgroundCursorColor; + final ValueNotifier showCursor; + final bool forceLine; + final bool readOnly; + final bool hasFocus; + final int? maxLines; + final int? minLines; + final bool expands; + final StrutStyle? strutStyle; + final Color? selectionColor; + final TextScaler textScaler; + final TextAlign textAlign; + final TextDirection textDirection; + final Locale? locale; + final String obscuringCharacter; + final bool obscureText; + final TextHeightBehavior? textHeightBehavior; + final TextWidthBasis textWidthBasis; + final ViewportOffset offset; + final bool rendererIgnoresPointer; + final double cursorWidth; + final double? cursorHeight; + final Radius? cursorRadius; + final Offset cursorOffset; + final bool paintCursorAboveText; + final ui.BoxHeightStyle selectionHeightStyle; + final ui.BoxWidthStyle selectionWidthStyle; + final bool enableInteractiveSelection; + final TextSelectionDelegate textSelectionDelegate; + final double devicePixelRatio; + final TextRange? promptRectRange; + final Color? promptRectColor; + final Clip clipBehavior; + + // zmtzawqlp + @override + _RenderEditable createRenderObject(BuildContext context) { + return _RenderEditable( + text: inlineSpan, + cursorColor: cursorColor, + startHandleLayerLink: startHandleLayerLink, + endHandleLayerLink: endHandleLayerLink, + backgroundCursorColor: backgroundCursorColor, + showCursor: showCursor, + forceLine: forceLine, + readOnly: readOnly, + hasFocus: hasFocus, + maxLines: maxLines, + minLines: minLines, + expands: expands, + strutStyle: strutStyle, + selectionColor: selectionColor, + textScaler: textScaler, + textAlign: textAlign, + textDirection: textDirection, + locale: locale ?? Localizations.maybeLocaleOf(context), + selection: value.selection, + offset: offset, + ignorePointer: rendererIgnoresPointer, + obscuringCharacter: obscuringCharacter, + obscureText: obscureText, + textHeightBehavior: textHeightBehavior, + textWidthBasis: textWidthBasis, + cursorWidth: cursorWidth, + cursorHeight: cursorHeight, + cursorRadius: cursorRadius, + cursorOffset: cursorOffset, + paintCursorAboveText: paintCursorAboveText, + selectionHeightStyle: selectionHeightStyle, + selectionWidthStyle: selectionWidthStyle, + enableInteractiveSelection: enableInteractiveSelection, + textSelectionDelegate: textSelectionDelegate, + devicePixelRatio: devicePixelRatio, + promptRectRange: promptRectRange, + promptRectColor: promptRectColor, + clipBehavior: clipBehavior, + ); + } + + // zmtzawqlp + @override + void updateRenderObject(BuildContext context, _RenderEditable renderObject) { + renderObject + ..text = inlineSpan + ..cursorColor = cursorColor + ..startHandleLayerLink = startHandleLayerLink + ..endHandleLayerLink = endHandleLayerLink + ..backgroundCursorColor = backgroundCursorColor + ..showCursor = showCursor + ..forceLine = forceLine + ..readOnly = readOnly + ..hasFocus = hasFocus + ..maxLines = maxLines + ..minLines = minLines + ..expands = expands + ..strutStyle = strutStyle + ..selectionColor = selectionColor + ..textScaler = textScaler + ..textAlign = textAlign + ..textDirection = textDirection + ..locale = locale ?? Localizations.maybeLocaleOf(context) + ..selection = value.selection + ..offset = offset + ..ignorePointer = rendererIgnoresPointer + ..textHeightBehavior = textHeightBehavior + ..textWidthBasis = textWidthBasis + ..obscuringCharacter = obscuringCharacter + ..obscureText = obscureText + ..cursorWidth = cursorWidth + ..cursorHeight = cursorHeight + ..cursorRadius = cursorRadius + ..cursorOffset = cursorOffset + ..selectionHeightStyle = selectionHeightStyle + ..selectionWidthStyle = selectionWidthStyle + ..enableInteractiveSelection = enableInteractiveSelection + ..textSelectionDelegate = textSelectionDelegate + ..devicePixelRatio = devicePixelRatio + ..paintCursorAboveText = paintCursorAboveText + ..promptRectColor = promptRectColor + ..clipBehavior = clipBehavior + ..setPromptRectRange(promptRectRange); + } +} + +@immutable +class _ScribbleCacheKey { + const _ScribbleCacheKey({ + required this.inlineSpan, + required this.textAlign, + required this.textDirection, + required this.textScaler, + required this.textHeightBehavior, + required this.locale, + required this.structStyle, + required this.placeholder, + required this.size, + }); + + final TextAlign textAlign; + final TextDirection textDirection; + final TextScaler textScaler; + final TextHeightBehavior? textHeightBehavior; + final Locale? locale; + final StrutStyle structStyle; + final int placeholder; + final Size size; + final InlineSpan inlineSpan; + + RenderComparison compare(_ScribbleCacheKey other) { + if (identical(other, this)) { + return RenderComparison.identical; + } + final bool needsLayout = textAlign != other.textAlign || + textDirection != other.textDirection || + textScaler != other.textScaler || + (textHeightBehavior ?? const TextHeightBehavior()) != + (other.textHeightBehavior ?? const TextHeightBehavior()) || + locale != other.locale || + structStyle != other.structStyle || + placeholder != other.placeholder || + size != other.size; + return needsLayout + ? RenderComparison.layout + : inlineSpan.compareTo(other.inlineSpan); + } +} + +class _ScribbleFocusable extends StatefulWidget { + const _ScribbleFocusable({ + required this.child, + required this.focusNode, + required this.editableKey, + required this.updateSelectionRects, + required this.enabled, + }); + + final Widget child; + final FocusNode focusNode; + final GlobalKey editableKey; + final VoidCallback updateSelectionRects; + final bool enabled; + + @override + _ScribbleFocusableState createState() => _ScribbleFocusableState(); +} + +class _ScribbleFocusableState extends State<_ScribbleFocusable> + implements ScribbleClient { + _ScribbleFocusableState() + : _elementIdentifier = (_nextElementIdentifier++).toString(); + + @override + void initState() { + super.initState(); + if (widget.enabled) { + TextInput.registerScribbleElement(elementIdentifier, this); + } + } + + @override + void didUpdateWidget(_ScribbleFocusable oldWidget) { + super.didUpdateWidget(oldWidget); + if (!oldWidget.enabled && widget.enabled) { + TextInput.registerScribbleElement(elementIdentifier, this); + } + + if (oldWidget.enabled && !widget.enabled) { + TextInput.unregisterScribbleElement(elementIdentifier); + } + } + + @override + void dispose() { + TextInput.unregisterScribbleElement(elementIdentifier); + super.dispose(); + } + + // zmtzawqlp + _RenderEditable? get renderEditable => + widget.editableKey.currentContext?.findRenderObject() as _RenderEditable?; + + static int _nextElementIdentifier = 1; + final String _elementIdentifier; + + @override + String get elementIdentifier => _elementIdentifier; + + @override + void onScribbleFocus(Offset offset) { + widget.focusNode.requestFocus(); + renderEditable?.selectPositionAt( + from: offset, cause: SelectionChangedCause.scribble); + widget.updateSelectionRects(); + } + + @override + bool isInScribbleRect(Rect rect) { + final Rect calculatedBounds = bounds; + if (renderEditable?.readOnly ?? false) { + return false; + } + if (calculatedBounds == Rect.zero) { + return false; + } + if (!calculatedBounds.overlaps(rect)) { + return false; + } + final Rect intersection = calculatedBounds.intersect(rect); + final HitTestResult result = HitTestResult(); + WidgetsBinding.instance + .hitTestInView(result, intersection.center, View.of(context).viewId); + return result.path + .any((HitTestEntry entry) => entry.target == renderEditable); + } + + @override + Rect get bounds { + final RenderBox? box = context.findRenderObject() as RenderBox?; + if (box == null || !mounted || !box.attached) { + return Rect.zero; + } + final Matrix4 transform = box.getTransformTo(null); + return MatrixUtils.transformRect( + transform, Rect.fromLTWH(0, 0, box.size.width, box.size.height)); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} + +class _ScribblePlaceholder extends WidgetSpan { + const _ScribblePlaceholder({ + required super.child, + required this.size, + }); + + /// The size of the span, used in place of adding a placeholder size to the [TextPainter]. + final Size size; + + @override + void build( + ui.ParagraphBuilder builder, { + TextScaler textScaler = TextScaler.noScaling, + List? dimensions, + }) { + assert(debugAssertIsValid()); + final bool hasStyle = style != null; + if (hasStyle) { + builder.pushStyle(style!.getTextStyle(textScaler: textScaler)); + } + builder.addPlaceholder(size.width, size.height, alignment); + if (hasStyle) { + builder.pop(); + } + } +} + +/// A text boundary that uses code points as logical boundaries. +/// +/// A code point represents a single character. This may be smaller than what is +/// represented by a user-perceived character, or grapheme. For example, a +/// single grapheme (in this case a Unicode extended grapheme cluster) like +/// "👨‍👩‍👦" consists of five code points: the man emoji, a zero +/// width joiner, the woman emoji, another zero width joiner, and the boy emoji. +/// The [String] has a length of eight because each emoji consists of two code +/// units. +/// +/// Code units are the units by which Dart's String class is measured, which is +/// encoded in UTF-16. +/// +/// See also: +/// +/// * [String.runes], which deals with code points like this class. +/// * [String.characters], which deals with graphemes. +/// * [CharacterBoundary], which is a [TextBoundary] like this class, but whose +/// boundaries are graphemes instead of code points. +class _CodePointBoundary extends TextBoundary { + const _CodePointBoundary(this._text); + + final String _text; + + // Returns true if the given position falls in the center of a surrogate pair. + bool _breaksSurrogatePair(int position) { + assert(position > 0 && position < _text.length && _text.length > 1); + return TextPainter.isHighSurrogate(_text.codeUnitAt(position - 1)) && + TextPainter.isLowSurrogate(_text.codeUnitAt(position)); + } + + @override + int? getLeadingTextBoundaryAt(int position) { + if (_text.isEmpty || position < 0) { + return null; + } + if (position == 0) { + return 0; + } + if (position >= _text.length) { + return _text.length; + } + if (_text.length <= 1) { + return position; + } + + return _breaksSurrogatePair(position) ? position - 1 : position; + } + + @override + int? getTrailingTextBoundaryAt(int position) { + if (_text.isEmpty || position >= _text.length) { + return null; + } + if (position < 0) { + return 0; + } + if (position == _text.length - 1) { + return _text.length; + } + if (_text.length <= 1) { + return position; + } + + return _breaksSurrogatePair(position + 1) ? position + 2 : position + 1; + } +} + +// ------------------------------- Text Actions ------------------------------- +class _DeleteTextAction + extends ContextAction { + _DeleteTextAction(this.state, this.getTextBoundary, this._applyTextBoundary); + + /// zmtzawqlp + final _EditableTextState state; + final TextBoundary Function() getTextBoundary; + final _ApplyTextBoundary _applyTextBoundary; + + @override + Object? invoke(T intent, [BuildContext? context]) { + final TextSelection selection = state._value.selection; + if (!selection.isValid) { + return null; + } + assert(selection.isValid); + // Expands the selection to ensure the range covers full graphemes. + final TextBoundary atomicBoundary = state._characterBoundary(); + if (!selection.isCollapsed) { + // Expands the selection to ensure the range covers full graphemes. + final TextRange range = TextRange( + start: atomicBoundary.getLeadingTextBoundaryAt(selection.start) ?? + state._value.text.length, + end: atomicBoundary.getTrailingTextBoundaryAt(selection.end - 1) ?? 0, + ); + return Actions.invoke( + context!, + ReplaceTextIntent( + state._value, '', range, SelectionChangedCause.keyboard), + ); + } + + final int target = + _applyTextBoundary(selection.base, intent.forward, getTextBoundary()) + .offset; + + final TextRange rangeToDelete = TextSelection( + baseOffset: intent.forward + ? atomicBoundary.getLeadingTextBoundaryAt(selection.baseOffset) ?? + state._value.text.length + : atomicBoundary + .getTrailingTextBoundaryAt(selection.baseOffset - 1) ?? + 0, + extentOffset: target, + ); + return Actions.invoke( + context!, + ReplaceTextIntent( + state._value, '', rangeToDelete, SelectionChangedCause.keyboard), + ); + } + + @override + bool get isActionEnabled => + !state.widget.readOnly && state._value.selection.isValid; +} + +class _UpdateTextSelectionAction + extends ContextAction { + _UpdateTextSelectionAction( + this.state, + this.getTextBoundary, + this.applyTextBoundary, { + required this.ignoreNonCollapsedSelection, + this.isExpand = false, + this.extentAtIndex = false, + }); + + /// zmtzawqlp + final _EditableTextState state; + final bool ignoreNonCollapsedSelection; + final bool isExpand; + final bool extentAtIndex; + final TextBoundary Function() getTextBoundary; + final _ApplyTextBoundary applyTextBoundary; + + static const int NEWLINE_CODE_UNIT = 10; + + // Returns true iff the given position is at a wordwrap boundary in the + // upstream position. + bool _isAtWordwrapUpstream(TextPosition position) { + final TextPosition end = TextPosition( + offset: state.renderEditable.getLineAtOffset(position).end, + affinity: TextAffinity.upstream, + ); + return end == position && + end.offset != state.textEditingValue.text.length && + state.textEditingValue.text.codeUnitAt(position.offset) != + NEWLINE_CODE_UNIT; + } + + // Returns true if the given position at a wordwrap boundary in the + // downstream position. + bool _isAtWordwrapDownstream(TextPosition position) { + final TextPosition start = TextPosition( + offset: state.renderEditable.getLineAtOffset(position).start, + ); + return start == position && + start.offset != 0 && + state.textEditingValue.text.codeUnitAt(position.offset - 1) != + NEWLINE_CODE_UNIT; + } + + @override + Object? invoke(T intent, [BuildContext? context]) { + final TextSelection selection = state._value.selection; + assert(selection.isValid); + + final bool collapseSelection = + intent.collapseSelection || !state.widget.selectionEnabled; + if (!selection.isCollapsed && + !ignoreNonCollapsedSelection && + collapseSelection) { + return Actions.invoke( + context!, + UpdateSelectionIntent( + state._value, + TextSelection.collapsed( + offset: intent.forward ? selection.end : selection.start), + SelectionChangedCause.keyboard, + )); + } + + TextPosition extent = selection.extent; + // If continuesAtWrap is true extent and is at the relevant wordwrap, then + // move it just to the other side of the wordwrap. + if (intent.continuesAtWrap) { + if (intent.forward && _isAtWordwrapUpstream(extent)) { + extent = TextPosition( + offset: extent.offset, + ); + } else if (!intent.forward && _isAtWordwrapDownstream(extent)) { + extent = TextPosition( + offset: extent.offset, + affinity: TextAffinity.upstream, + ); + } + } + + final bool shouldTargetBase = isExpand && + (intent.forward + ? selection.baseOffset > selection.extentOffset + : selection.baseOffset < selection.extentOffset); + final TextPosition newExtent = applyTextBoundary( + shouldTargetBase ? selection.base : extent, + intent.forward, + getTextBoundary()); + final TextSelection newSelection = collapseSelection || + (!isExpand && newExtent.offset == selection.baseOffset) + ? TextSelection.fromPosition(newExtent) + : isExpand + ? selection.expandTo( + newExtent, extentAtIndex || selection.isCollapsed) + : selection.extendTo(newExtent); + + final bool shouldCollapseToBase = intent.collapseAtReversal && + (selection.baseOffset - selection.extentOffset) * + (selection.baseOffset - newSelection.extentOffset) < + 0; + final TextSelection newRange = shouldCollapseToBase + ? TextSelection.fromPosition(selection.base) + : newSelection; + return Actions.invoke( + context!, + UpdateSelectionIntent( + state._value, newRange, SelectionChangedCause.keyboard)); + } + + @override + bool get isActionEnabled => state._value.selection.isValid; +} + +class _UpdateTextSelectionVerticallyAction< + T extends DirectionalCaretMovementIntent> extends ContextAction { + _UpdateTextSelectionVerticallyAction(this.state); + + /// zmtzawqlp + final _EditableTextState state; + + VerticalCaretMovementRun? _verticalMovementRun; + TextSelection? _runSelection; + + void stopCurrentVerticalRunIfSelectionChanges() { + final TextSelection? runSelection = _runSelection; + if (runSelection == null) { + assert(_verticalMovementRun == null); + return; + } + _runSelection = state._value.selection; + final TextSelection currentSelection = state.widget.controller.selection; + final bool continueCurrentRun = currentSelection.isValid && + currentSelection.isCollapsed && + currentSelection.baseOffset == runSelection.baseOffset && + currentSelection.extentOffset == runSelection.extentOffset; + if (!continueCurrentRun) { + _verticalMovementRun = null; + _runSelection = null; + } + } + + @override + void invoke(T intent, [BuildContext? context]) { + assert(state._value.selection.isValid); + + final bool collapseSelection = + intent.collapseSelection || !state.widget.selectionEnabled; + final TextEditingValue value = state._textEditingValueforTextLayoutMetrics; + if (!value.selection.isValid) { + return; + } + + if (_verticalMovementRun?.isValid == false) { + _verticalMovementRun = null; + _runSelection = null; + } + + final VerticalCaretMovementRun currentRun = _verticalMovementRun ?? + state.renderEditable + .startVerticalCaretMovement(state.renderEditable.selection!.extent); + + final bool shouldMove = intent + is ExtendSelectionVerticallyToAdjacentPageIntent + ? currentRun.moveByOffset( + (intent.forward ? 1.0 : -1.0) * state.renderEditable.size.height) + : intent.forward + ? currentRun.moveNext() + : currentRun.movePrevious(); + final TextPosition newExtent = shouldMove + ? currentRun.current + : intent.forward + ? TextPosition(offset: value.text.length) + : const TextPosition(offset: 0); + final TextSelection newSelection = collapseSelection + ? TextSelection.fromPosition(newExtent) + : value.selection.extendTo(newExtent); + + Actions.invoke( + context!, + UpdateSelectionIntent( + value, newSelection, SelectionChangedCause.keyboard), + ); + if (state._value.selection == newSelection) { + _verticalMovementRun = currentRun; + _runSelection = newSelection; + } + } + + @override + bool get isActionEnabled => state._value.selection.isValid; +} + +class _SelectAllAction extends ContextAction { + _SelectAllAction(this.state); + + /// zmtzawqlp + final _EditableTextState state; + + @override + Object? invoke(SelectAllTextIntent intent, [BuildContext? context]) { + return Actions.invoke( + context!, + UpdateSelectionIntent( + state._value, + TextSelection(baseOffset: 0, extentOffset: state._value.text.length), + intent.cause, + ), + ); + } + + @override + bool get isActionEnabled => state.widget.selectionEnabled; +} + +class _CopySelectionAction extends ContextAction { + _CopySelectionAction(this.state); + + /// zmtzawqlp + final _EditableTextState state; + + @override + void invoke(CopySelectionTextIntent intent, [BuildContext? context]) { + if (intent.collapseSelection) { + state.cutSelection(intent.cause); + } else { + state.copySelection(intent.cause); + } + } + + @override + bool get isActionEnabled => + state._value.selection.isValid && !state._value.selection.isCollapsed; +} + +/// A [ClipboardStatusNotifier] whose [value] is hardcoded to +/// [ClipboardStatus.pasteable]. +/// +/// Useful to avoid showing a permission dialog on web, which happens when +/// [Clipboard.hasStrings] is called. +class _WebClipboardStatusNotifier extends ClipboardStatusNotifier { + @override + ClipboardStatus value = ClipboardStatus.pasteable; + + @override + Future update() { + return Future.value(); + } +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/official/widgets/spell_check.dart b/local_packages/extended_text_field-16.0.2/lib/src/official/widgets/spell_check.dart new file mode 100644 index 0000000..627e392 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/official/widgets/spell_check.dart @@ -0,0 +1,419 @@ +part of 'package:extended_text_field/src/extended/widgets/text_field.dart'; + +/// [SpellCheckConfiguration] +/// Controls how spell check is performed for text input. +/// +/// This configuration determines the [SpellCheckService] used to fetch the +/// [List] spell check results and the [TextStyle] used to +/// mark misspelled words within text input. +@immutable +class _SpellCheckConfiguration { + /// Creates a configuration that specifies the service and suggestions handler + /// for spell check. + const _SpellCheckConfiguration({ + this.spellCheckService, + this.misspelledSelectionColor, + this.misspelledTextStyle, + this.spellCheckSuggestionsToolbarBuilder, + }) : _spellCheckEnabled = true; + + /// Creates a configuration that disables spell check. + const _SpellCheckConfiguration.disabled() + : _spellCheckEnabled = false, + spellCheckService = null, + spellCheckSuggestionsToolbarBuilder = null, + misspelledTextStyle = null, + misspelledSelectionColor = null; + + /// The service used to fetch spell check results for text input. + final SpellCheckService? spellCheckService; + + /// The color the paint the selection highlight when spell check is showing + /// suggestions for a misspelled word. + /// + /// For example, on iOS, the selection appears red while the spell check menu + /// is showing. + final Color? misspelledSelectionColor; + + /// Style used to indicate misspelled words. + /// + /// This is nullable to allow style-specific wrappers of [EditableText] + /// to infer this, but this must be specified if this configuration is + /// provided directly to [EditableText] or its construction will fail with an + /// assertion error. + final TextStyle? misspelledTextStyle; + + /// Builds the toolbar used to display spell check suggestions for misspelled + /// words. + final EditableTextContextMenuBuilder? spellCheckSuggestionsToolbarBuilder; + + final bool _spellCheckEnabled; + + /// Whether or not the configuration should enable or disable spell check. + bool get spellCheckEnabled => _spellCheckEnabled; + + /// Returns a copy of the current [_SpellCheckConfiguration] instance with + /// specified overrides. + _SpellCheckConfiguration copyWith( + {SpellCheckService? spellCheckService, + Color? misspelledSelectionColor, + TextStyle? misspelledTextStyle, + EditableTextContextMenuBuilder? spellCheckSuggestionsToolbarBuilder}) { + if (!_spellCheckEnabled) { + // A new configuration should be constructed to enable spell check. + return const _SpellCheckConfiguration.disabled(); + } + + return _SpellCheckConfiguration( + spellCheckService: spellCheckService ?? this.spellCheckService, + misspelledSelectionColor: + misspelledSelectionColor ?? this.misspelledSelectionColor, + misspelledTextStyle: misspelledTextStyle ?? this.misspelledTextStyle, + spellCheckSuggestionsToolbarBuilder: + spellCheckSuggestionsToolbarBuilder ?? + this.spellCheckSuggestionsToolbarBuilder, + ); + } + + @override + String toString() { + return '${objectRuntimeType(this, 'SpellCheckConfiguration')}(' + '${_spellCheckEnabled ? 'enabled' : 'disabled'}, ' + 'service: $spellCheckService, ' + 'text style: $misspelledTextStyle, ' + 'toolbar builder: $spellCheckSuggestionsToolbarBuilder' + ')'; + } + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + + return other is _SpellCheckConfiguration && + other.spellCheckService == spellCheckService && + other.misspelledTextStyle == misspelledTextStyle && + other.spellCheckSuggestionsToolbarBuilder == + spellCheckSuggestionsToolbarBuilder && + other._spellCheckEnabled == _spellCheckEnabled; + } + + @override + int get hashCode => Object.hash(spellCheckService, misspelledTextStyle, + spellCheckSuggestionsToolbarBuilder, _spellCheckEnabled); +} + +// Methods for displaying spell check results: + +/// Adjusts spell check results to correspond to [newText] if the only results +/// that the handler has access to are the [results] corresponding to +/// [resultsText]. +/// +/// Used in the case where the request for the spell check results of the +/// [newText] is lagging in order to avoid display of incorrect results. +List _correctSpellCheckResults( + String newText, String resultsText, List results) { + final List correctedSpellCheckResults = []; + int spanPointer = 0; + int offset = 0; + + // Assumes that the order of spans has not been jumbled for optimization + // purposes, and will only search since the previously found span. + int searchStart = 0; + + while (spanPointer < results.length) { + final SuggestionSpan currentSpan = results[spanPointer]; + final String currentSpanText = + resultsText.substring(currentSpan.range.start, currentSpan.range.end); + final int spanLength = currentSpan.range.end - currentSpan.range.start; + + // Try finding SuggestionSpan from resultsText in new text. + final String escapedText = RegExp.escape(currentSpanText); + final RegExp currentSpanTextRegexp = RegExp('\\b$escapedText\\b'); + final int foundIndex = + newText.substring(searchStart).indexOf(currentSpanTextRegexp); + + // Check whether word was found exactly where expected or elsewhere in the newText. + final bool currentSpanFoundExactly = + currentSpan.range.start == foundIndex + searchStart; + final bool currentSpanFoundExactlyWithOffset = + currentSpan.range.start + offset == foundIndex + searchStart; + final bool currentSpanFoundElsewhere = foundIndex >= 0; + + if (currentSpanFoundExactly || currentSpanFoundExactlyWithOffset) { + // currentSpan was found at the same index in newText and resultsText + // or at the same index with the previously calculated adjustment by + // the offset value, so apply it to new text by adding it to the list of + // corrected results. + final SuggestionSpan adjustedSpan = SuggestionSpan( + TextRange( + start: currentSpan.range.start + offset, + end: currentSpan.range.end + offset, + ), + currentSpan.suggestions, + ); + + // Start search for the next misspelled word at the end of currentSpan. + searchStart = currentSpan.range.end + 1 + offset; + correctedSpellCheckResults.add(adjustedSpan); + } else if (currentSpanFoundElsewhere) { + // Word was pushed forward but not modified. + final int adjustedSpanStart = searchStart + foundIndex; + final int adjustedSpanEnd = adjustedSpanStart + spanLength; + final SuggestionSpan adjustedSpan = SuggestionSpan( + TextRange(start: adjustedSpanStart, end: adjustedSpanEnd), + currentSpan.suggestions, + ); + + // Start search for the next misspelled word at the end of the + // adjusted currentSpan. + searchStart = adjustedSpanEnd + 1; + // Adjust offset to reflect the difference between where currentSpan + // was positioned in resultsText versus in newText. + offset = adjustedSpanStart - currentSpan.range.start; + correctedSpellCheckResults.add(adjustedSpan); + } + spanPointer++; + } + return correctedSpellCheckResults; +} + +/// Builds the [TextSpan] tree given the current state of the text input and +/// spell check results. +/// +/// The [value] is the current [TextEditingValue] requested to be rendered +/// by a text input widget. The [composingWithinCurrentTextRange] value +/// represents whether or not there is a valid composing region in the +/// [value]. The [style] is the [TextStyle] to render the [value]'s text with, +/// and the [misspelledTextStyle] is the [TextStyle] to render misspelled +/// words within the [value]'s text with. The [spellCheckResults] are the +/// results of spell checking the [value]'s text. +TextSpan buildTextSpanWithSpellCheckSuggestions( + TextEditingValue value, + bool composingWithinCurrentTextRange, + TextStyle? style, + TextStyle misspelledTextStyle, + SpellCheckResults spellCheckResults) { + List spellCheckResultsSpans = + spellCheckResults.suggestionSpans; + final String spellCheckResultsText = spellCheckResults.spellCheckedText; + + if (spellCheckResultsText != value.text) { + spellCheckResultsSpans = _correctSpellCheckResults( + value.text, spellCheckResultsText, spellCheckResultsSpans); + } + + // We will draw the TextSpan tree based on the composing region, if it is + // available. + // TODO(camsim99): The two separate strategies for building TextSpan trees + // based on the availability of a composing region should be merged: + // https://github.com/flutter/flutter/issues/124142. + final bool shouldConsiderComposingRegion = + defaultTargetPlatform == TargetPlatform.android; + if (shouldConsiderComposingRegion) { + return TextSpan( + style: style, + children: _buildSubtreesWithComposingRegion( + spellCheckResultsSpans, + value, + style, + misspelledTextStyle, + composingWithinCurrentTextRange, + ), + ); + } + + return TextSpan( + style: style, + children: _buildSubtreesWithoutComposingRegion( + spellCheckResultsSpans, + value, + style, + misspelledTextStyle, + value.selection.baseOffset, + ), + ); +} + +/// Builds the [TextSpan] tree for spell check without considering the composing +/// region. Instead, uses the cursor to identify the word that's actively being +/// edited and shouldn't be spell checked. This is useful for platforms and IMEs +/// that don't use the composing region for the active word. +List _buildSubtreesWithoutComposingRegion( + List? spellCheckSuggestions, + TextEditingValue value, + TextStyle? style, + TextStyle misspelledStyle, + int cursorIndex, +) { + final List textSpanTreeChildren = []; + + int textPointer = 0; + int currentSpanPointer = 0; + int endIndex; + final String text = value.text; + final TextStyle misspelledJointStyle = + style?.merge(misspelledStyle) ?? misspelledStyle; + bool cursorInCurrentSpan = false; + + // Add text interwoven with any misspelled words to the tree. + if (spellCheckSuggestions != null) { + while (textPointer < text.length && + currentSpanPointer < spellCheckSuggestions.length) { + final SuggestionSpan currentSpan = + spellCheckSuggestions[currentSpanPointer]; + + if (currentSpan.range.start > textPointer) { + endIndex = currentSpan.range.start < text.length + ? currentSpan.range.start + : text.length; + textSpanTreeChildren.add(TextSpan( + style: style, + text: text.substring(textPointer, endIndex), + )); + textPointer = endIndex; + } else { + endIndex = currentSpan.range.end < text.length + ? currentSpan.range.end + : text.length; + cursorInCurrentSpan = currentSpan.range.start <= cursorIndex && + currentSpan.range.end >= cursorIndex; + textSpanTreeChildren.add(TextSpan( + style: cursorInCurrentSpan ? style : misspelledJointStyle, + text: text.substring(currentSpan.range.start, endIndex), + )); + + textPointer = endIndex; + currentSpanPointer++; + } + } + } + + // Add any remaining text to the tree if applicable. + if (textPointer < text.length) { + textSpanTreeChildren.add(TextSpan( + style: style, + text: text.substring(textPointer, text.length), + )); + } + + return textSpanTreeChildren; +} + +/// Builds [TextSpan] subtree for text with misspelled words with logic based on +/// a valid composing region. +List _buildSubtreesWithComposingRegion( + List? spellCheckSuggestions, + TextEditingValue value, + TextStyle? style, + TextStyle misspelledStyle, + bool composingWithinCurrentTextRange) { + final List textSpanTreeChildren = []; + + int textPointer = 0; + int currentSpanPointer = 0; + int endIndex; + SuggestionSpan currentSpan; + final String text = value.text; + final TextRange composingRegion = value.composing; + final TextStyle composingTextStyle = + style?.merge(const TextStyle(decoration: TextDecoration.underline)) ?? + const TextStyle(decoration: TextDecoration.underline); + final TextStyle misspelledJointStyle = + style?.merge(misspelledStyle) ?? misspelledStyle; + bool textPointerWithinComposingRegion = false; + bool currentSpanIsComposingRegion = false; + + // Add text interwoven with any misspelled words to the tree. + if (spellCheckSuggestions != null) { + while (textPointer < text.length && + currentSpanPointer < spellCheckSuggestions.length) { + currentSpan = spellCheckSuggestions[currentSpanPointer]; + + if (currentSpan.range.start > textPointer) { + endIndex = currentSpan.range.start < text.length + ? currentSpan.range.start + : text.length; + textPointerWithinComposingRegion = + composingRegion.start >= textPointer && + composingRegion.end <= endIndex && + !composingWithinCurrentTextRange; + + if (textPointerWithinComposingRegion) { + _addComposingRegionTextSpans(textSpanTreeChildren, text, textPointer, + composingRegion, style, composingTextStyle); + textSpanTreeChildren.add(TextSpan( + style: style, + text: text.substring(composingRegion.end, endIndex), + )); + } else { + textSpanTreeChildren.add(TextSpan( + style: style, + text: text.substring(textPointer, endIndex), + )); + } + + textPointer = endIndex; + } else { + endIndex = currentSpan.range.end < text.length + ? currentSpan.range.end + : text.length; + currentSpanIsComposingRegion = textPointer >= composingRegion.start && + endIndex <= composingRegion.end && + !composingWithinCurrentTextRange; + textSpanTreeChildren.add(TextSpan( + style: currentSpanIsComposingRegion + ? composingTextStyle + : misspelledJointStyle, + text: text.substring(currentSpan.range.start, endIndex), + )); + + textPointer = endIndex; + currentSpanPointer++; + } + } + } + + // Add any remaining text to the tree if applicable. + if (textPointer < text.length) { + if (textPointer < composingRegion.start && + !composingWithinCurrentTextRange) { + _addComposingRegionTextSpans(textSpanTreeChildren, text, textPointer, + composingRegion, style, composingTextStyle); + + if (composingRegion.end != text.length) { + textSpanTreeChildren.add(TextSpan( + style: style, + text: text.substring(composingRegion.end, text.length), + )); + } + } else { + textSpanTreeChildren.add(TextSpan( + style: style, + text: text.substring(textPointer, text.length), + )); + } + } + + return textSpanTreeChildren; +} + +/// Helper method to create [TextSpan] tree children for specified range of +/// text up to and including the composing region. +void _addComposingRegionTextSpans( + List treeChildren, + String text, + int start, + TextRange composingRegion, + TextStyle? style, + TextStyle composingTextStyle) { + treeChildren.add(TextSpan( + style: style, + text: text.substring(start, composingRegion.start), + )); + treeChildren.add(TextSpan( + style: composingTextStyle, + text: text.substring(composingRegion.start, composingRegion.end), + )); +} diff --git a/local_packages/extended_text_field-16.0.2/lib/src/official/widgets/text_field.dart b/local_packages/extended_text_field-16.0.2/lib/src/official/widgets/text_field.dart new file mode 100644 index 0000000..76b93a6 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/official/widgets/text_field.dart @@ -0,0 +1,1804 @@ +part of 'package:extended_text_field/src/extended/widgets/text_field.dart'; + +/// [TextField] +class _TextFieldSelectionGestureDetectorBuilder + extends _TextSelectionGestureDetectorBuilder { + _TextFieldSelectionGestureDetectorBuilder({ + required _TextFieldState state, + }) : _state = state, + super(delegate: state); + + final _TextFieldState _state; + + @override + void onForcePressStart(ForcePressDetails details) { + super.onForcePressStart(details); + if (delegate.selectionEnabled && shouldShowSelectionToolbar) { + editableText.showToolbar(); + } + } + + @override + void onForcePressEnd(ForcePressDetails details) { + // Not required. + } + + @override + bool get onUserTapAlwaysCalled => _state.widget.onTapAlwaysCalled; + + @override + void onUserTap() { + _state.widget.onTap?.call(); + } + + @override + void onSingleLongTapStart(LongPressStartDetails details) { + super.onSingleLongTapStart(details); + if (delegate.selectionEnabled) { + switch (Theme.of(_state.context).platform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + break; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + Feedback.forLongPress(_state.context); + } + } + } +} + +/// A Material Design text field. +/// +/// A text field lets the user enter text, either with hardware keyboard or with +/// an onscreen keyboard. +/// +/// The text field calls the [onChanged] callback whenever the user changes the +/// text in the field. If the user indicates that they are done typing in the +/// field (e.g., by pressing a button on the soft keyboard), the text field +/// calls the [onSubmitted] callback. +/// +/// To control the text that is displayed in the text field, use the +/// [controller]. For example, to set the initial value of the text field, use +/// a [controller] that already contains some text. The [controller] can also +/// control the selection and composing region (and to observe changes to the +/// text, selection, and composing region). +/// +/// By default, a text field has a [decoration] that draws a divider below the +/// text field. You can use the [decoration] property to control the decoration, +/// for example by adding a label or an icon. If you set the [decoration] +/// property to null, the decoration will be removed entirely, including the +/// extra padding introduced by the decoration to save space for the labels. +/// +/// If [decoration] is non-null (which is the default), the text field requires +/// one of its ancestors to be a [Material] widget. +/// +/// To integrate the [TextField] into a [Form] with other [FormField] widgets, +/// consider using [TextFormField]. +/// +/// {@template flutter.material.textfield.wantKeepAlive} +/// When the widget has focus, it will prevent itself from disposing via its +/// underlying [EditableText]'s [AutomaticKeepAliveClientMixin.wantKeepAlive] in +/// order to avoid losing the selection. Removing the focus will allow it to be +/// disposed. +/// {@endtemplate} +/// +/// Remember to call [TextEditingController.dispose] on the [TextEditingController] +/// when it is no longer needed. This will ensure we discard any resources used +/// by the object. +/// +/// If this field is part of a scrolling container that lazily constructs its +/// children, like a [ListView] or a [CustomScrollView], then a [controller] +/// should be specified. The controller's lifetime should be managed by a +/// stateful widget ancestor of the scrolling container. +/// +/// ## Obscured Input +/// +/// {@tool dartpad} +/// This example shows how to create a [TextField] that will obscure input. The +/// [InputDecoration] surrounds the field in a border using [OutlineInputBorder] +/// and adds a label. +/// +/// ** See code in examples/api/lib/material/text_field/text_field.0.dart ** +/// {@end-tool} +/// +/// ## Reading values +/// +/// A common way to read a value from a TextField is to use the [onSubmitted] +/// callback. This callback is applied to the text field's current value when +/// the user finishes editing. +/// +/// {@tool dartpad} +/// This sample shows how to get a value from a TextField via the [onSubmitted] +/// callback. +/// +/// ** See code in examples/api/lib/material/text_field/text_field.1.dart ** +/// {@end-tool} +/// +/// {@macro flutter.widgets.EditableText.lifeCycle} +/// +/// For most applications the [onSubmitted] callback will be sufficient for +/// reacting to user input. +/// +/// The [onEditingComplete] callback also runs when the user finishes editing. +/// It's different from [onSubmitted] because it has a default value which +/// updates the text controller and yields the keyboard focus. Applications that +/// require different behavior can override the default [onEditingComplete] +/// callback. +/// +/// Keep in mind you can also always read the current string from a TextField's +/// [TextEditingController] using [TextEditingController.text]. +/// +/// ## Handling emojis and other complex characters +/// {@macro flutter.widgets.EditableText.onChanged} +/// +/// In the live Dartpad example above, try typing the emoji 👨‍👩‍👦 +/// into the field and submitting. Because the example code measures the length +/// with `value.characters.length`, the emoji is correctly counted as a single +/// character. +/// +/// {@macro flutter.widgets.editableText.showCaretOnScreen} +/// +/// {@macro flutter.widgets.editableText.accessibility} +/// +/// {@tool dartpad} +/// This sample shows how to style a text field to match a filled or outlined +/// Material Design 3 text field. +/// +/// ** See code in examples/api/lib/material/text_field/text_field.2.dart ** +/// {@end-tool} +/// +/// ## Scrolling Considerations +/// +/// If this [TextField] is not a descendant of [Scaffold] and is being used +/// within a [Scrollable] or nested [Scrollable]s, consider placing a +/// [ScrollNotificationObserver] above the root [Scrollable] that contains this +/// [TextField] to ensure proper scroll coordination for [TextField] and its +/// components like [TextSelectionOverlay]. +/// +/// See also: +/// +/// * [TextFormField], which integrates with the [Form] widget. +/// * [InputDecorator], which shows the labels and other visual elements that +/// surround the actual text editing widget. +/// * [EditableText], which is the raw text editing control at the heart of a +/// [TextField]. The [EditableText] widget is rarely used directly unless +/// you are implementing an entirely different design language, such as +/// Cupertino. +/// * +/// * Cookbook: [Create and style a text field](https://docs.flutter.dev/cookbook/forms/text-input) +/// * Cookbook: [Handle changes to a text field](https://docs.flutter.dev/cookbook/forms/text-field-changes) +/// * Cookbook: [Retrieve the value of a text field](https://docs.flutter.dev/cookbook/forms/retrieve-input) +/// * Cookbook: [Focus and text fields](https://docs.flutter.dev/cookbook/forms/focus) +class _TextField extends StatefulWidget { + /// Creates a Material Design text field. + /// + /// If [decoration] is non-null (which is the default), the text field requires + /// one of its ancestors to be a [Material] widget. + /// + /// To remove the decoration entirely (including the extra padding introduced + /// by the decoration to save space for the labels), set the [decoration] to + /// null. + /// + /// The [maxLines] property can be set to null to remove the restriction on + /// the number of lines. By default, it is one, meaning this is a single-line + /// text field. [maxLines] must not be zero. + /// + /// The [maxLength] property is set to null by default, which means the + /// number of characters allowed in the text field is not restricted. If + /// [maxLength] is set a character counter will be displayed below the + /// field showing how many characters have been entered. If the value is + /// set to a positive integer it will also display the maximum allowed + /// number of characters to be entered. If the value is set to + /// [TextField.noMaxLength] then only the current length is displayed. + /// + /// After [maxLength] characters have been input, additional input + /// is ignored, unless [maxLengthEnforcement] is set to + /// [MaxLengthEnforcement.none]. + /// The text field enforces the length with a [LengthLimitingTextInputFormatter], + /// which is evaluated after the supplied [inputFormatters], if any. + /// The [maxLength] value must be either null or greater than zero. + /// + /// If [maxLengthEnforcement] is set to [MaxLengthEnforcement.none], then more + /// than [maxLength] characters may be entered, and the error counter and + /// divider will switch to the [decoration].errorStyle when the limit is + /// exceeded. + /// + /// The text cursor is not shown if [showCursor] is false or if [showCursor] + /// is null (the default) and [readOnly] is true. + /// + /// The [selectionHeightStyle] and [selectionWidthStyle] properties allow + /// changing the shape of the selection highlighting. These properties default + /// to [ui.BoxHeightStyle.tight] and [ui.BoxWidthStyle.tight], respectively. + /// + /// See also: + /// + /// * [maxLength], which discusses the precise meaning of "number of + /// characters" and how it may differ from the intuitive meaning. + const _TextField({ + super.key, + this.groupId = EditableText, + this.controller, + this.focusNode, + this.undoController, + this.decoration = const InputDecoration(), + TextInputType? keyboardType, + this.textInputAction, + this.textCapitalization = TextCapitalization.none, + this.style, + this.strutStyle, + this.textAlign = TextAlign.start, + this.textAlignVertical, + this.textDirection, + this.readOnly = false, + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + this.toolbarOptions, + this.showCursor, + this.autofocus = false, + this.statesController, + this.obscuringCharacter = '•', + this.obscureText = false, + this.autocorrect = true, + SmartDashesType? smartDashesType, + SmartQuotesType? smartQuotesType, + this.enableSuggestions = true, + this.maxLines = 1, + this.minLines, + this.expands = false, + this.maxLength, + this.maxLengthEnforcement, + this.onChanged, + this.onEditingComplete, + this.onSubmitted, + this.onAppPrivateCommand, + this.inputFormatters, + this.enabled, + this.ignorePointers, + this.cursorWidth = 2.0, + this.cursorHeight, + this.cursorRadius, + this.cursorOpacityAnimates, + this.cursorColor, + this.cursorErrorColor, + this.selectionHeightStyle = ui.BoxHeightStyle.tight, + this.selectionWidthStyle = ui.BoxWidthStyle.tight, + this.keyboardAppearance, + this.scrollPadding = const EdgeInsets.all(20.0), + this.dragStartBehavior = DragStartBehavior.start, + bool? enableInteractiveSelection, + this.selectionControls, + this.onTap, + this.onTapAlwaysCalled = false, + this.onTapOutside, + this.mouseCursor, + this.buildCounter, + this.scrollController, + this.scrollPhysics, + this.autofillHints = const [], + this.contentInsertionConfiguration, + this.clipBehavior = Clip.hardEdge, + this.restorationId, + this.scribbleEnabled = true, + this.enableIMEPersonalizedLearning = true, + this.contextMenuBuilder = _defaultContextMenuBuilder, + this.canRequestFocus = true, + this.spellCheckConfiguration, + this.magnifierConfiguration, + }) : assert(obscuringCharacter.length == 1), + smartDashesType = smartDashesType ?? + (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled), + smartQuotesType = smartQuotesType ?? + (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled), + assert(maxLines == null || maxLines > 0), + assert(minLines == null || minLines > 0), + assert( + (maxLines == null) || (minLines == null) || (maxLines >= minLines), + "minLines can't be greater than maxLines", + ), + assert( + !expands || (maxLines == null && minLines == null), + 'minLines and maxLines must be null when expands is true.', + ), + assert(!obscureText || maxLines == 1, + 'Obscured fields cannot be multiline.'), + assert(maxLength == null || + maxLength == _TextField.noMaxLength || + maxLength > 0), + // Assert the following instead of setting it directly to avoid surprising the user by silently changing the value they set. + assert( + !identical(textInputAction, TextInputAction.newline) || + maxLines == 1 || + !identical(keyboardType, TextInputType.text), + 'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.', + ), + keyboardType = keyboardType ?? + (maxLines == 1 ? TextInputType.text : TextInputType.multiline), + enableInteractiveSelection = + enableInteractiveSelection ?? (!readOnly || !obscureText); + + /// The configuration for the magnifier of this text field. + /// + /// By default, builds a [CupertinoTextMagnifier] on iOS and [TextMagnifier] + /// on Android, and builds nothing on all other platforms. To suppress the + /// magnifier, consider passing [TextMagnifierConfiguration.disabled]. + /// + /// {@macro flutter.widgets.magnifier.intro} + /// + /// {@tool dartpad} + /// This sample demonstrates how to customize the magnifier that this text field uses. + /// + /// ** See code in examples/api/lib/widgets/text_magnifier/text_magnifier.0.dart ** + /// {@end-tool} + final TextMagnifierConfiguration? magnifierConfiguration; + + /// {@macro flutter.widgets.editableText.groupId} + final Object groupId; + + /// Controls the text being edited. + /// + /// If null, this widget will create its own [TextEditingController]. + final TextEditingController? controller; + + /// Defines the keyboard focus for this widget. + /// + /// The [focusNode] is a long-lived object that's typically managed by a + /// [StatefulWidget] parent. See [FocusNode] for more information. + /// + /// To give the keyboard focus to this widget, provide a [focusNode] and then + /// use the current [FocusScope] to request the focus: + /// + /// ```dart + /// FocusScope.of(context).requestFocus(myFocusNode); + /// ``` + /// + /// This happens automatically when the widget is tapped. + /// + /// To be notified when the widget gains or loses the focus, add a listener + /// to the [focusNode]: + /// + /// ```dart + /// myFocusNode.addListener(() { print(myFocusNode.hasFocus); }); + /// ``` + /// + /// If null, this widget will create its own [FocusNode]. + /// + /// ## Keyboard + /// + /// Requesting the focus will typically cause the keyboard to be shown + /// if it's not showing already. + /// + /// On Android, the user can hide the keyboard - without changing the focus - + /// with the system back button. They can restore the keyboard's visibility + /// by tapping on a text field. The user might hide the keyboard and + /// switch to a physical keyboard, or they might just need to get it + /// out of the way for a moment, to expose something it's + /// obscuring. In this case requesting the focus again will not + /// cause the focus to change, and will not make the keyboard visible. + /// + /// This widget builds an [EditableText] and will ensure that the keyboard is + /// showing when it is tapped by calling [EditableTextState.requestKeyboard()]. + final FocusNode? focusNode; + + /// The decoration to show around the text field. + /// + /// By default, draws a horizontal line under the text field but can be + /// configured to show an icon, label, hint text, and error text. + /// + /// Specify null to remove the decoration entirely (including the + /// extra padding introduced by the decoration to save space for the labels). + final InputDecoration? decoration; + + /// {@macro flutter.widgets.editableText.keyboardType} + final TextInputType keyboardType; + + /// {@template flutter.widgets.TextField.textInputAction} + /// The type of action button to use for the keyboard. + /// + /// Defaults to [TextInputAction.newline] if [keyboardType] is + /// [TextInputType.multiline] and [TextInputAction.done] otherwise. + /// {@endtemplate} + final TextInputAction? textInputAction; + + /// {@macro flutter.widgets.editableText.textCapitalization} + final TextCapitalization textCapitalization; + + /// The style to use for the text being edited. + /// + /// This text style is also used as the base style for the [decoration]. + /// + /// If null, [TextTheme.bodyLarge] will be used. When the text field is disabled, + /// [TextTheme.bodyLarge] with an opacity of 0.38 will be used instead. + /// + /// If null and [ThemeData.useMaterial3] is false, [TextTheme.titleMedium] will + /// be used. When the text field is disabled, [TextTheme.titleMedium] with + /// [ThemeData.disabledColor] will be used instead. + final TextStyle? style; + + /// {@macro flutter.widgets.editableText.strutStyle} + final StrutStyle? strutStyle; + + /// {@macro flutter.widgets.editableText.textAlign} + final TextAlign textAlign; + + /// {@macro flutter.material.InputDecorator.textAlignVertical} + final TextAlignVertical? textAlignVertical; + + /// {@macro flutter.widgets.editableText.textDirection} + final TextDirection? textDirection; + + /// {@macro flutter.widgets.editableText.autofocus} + final bool autofocus; + + /// Represents the interactive "state" of this widget in terms of a set of + /// [MaterialState]s, including [MaterialState.disabled], [MaterialState.hovered], + /// [MaterialState.error], and [MaterialState.focused]. + /// + /// Classes based on this one can provide their own + /// [MaterialStatesController] to which they've added listeners. + /// They can also update the controller's [MaterialStatesController.value] + /// however, this may only be done when it's safe to call + /// [State.setState], like in an event handler. + /// + /// The controller's [MaterialStatesController.value] represents the set of + /// states that a widget's visual properties, typically [MaterialStateProperty] + /// values, are resolved against. It is _not_ the intrinsic state of the widget. + /// The widget is responsible for ensuring that the controller's + /// [MaterialStatesController.value] tracks its intrinsic state. For example + /// one cannot request the keyboard focus for a widget by adding [MaterialState.focused] + /// to its controller. When the widget gains the or loses the focus it will + /// [MaterialStatesController.update] its controller's [MaterialStatesController.value] + /// and notify listeners of the change. + final MaterialStatesController? statesController; + + /// {@macro flutter.widgets.editableText.obscuringCharacter} + final String obscuringCharacter; + + /// {@macro flutter.widgets.editableText.obscureText} + final bool obscureText; + + /// {@macro flutter.widgets.editableText.autocorrect} + final bool autocorrect; + + /// {@macro flutter.services.TextInputConfiguration.smartDashesType} + final SmartDashesType smartDashesType; + + /// {@macro flutter.services.TextInputConfiguration.smartQuotesType} + final SmartQuotesType smartQuotesType; + + /// {@macro flutter.services.TextInputConfiguration.enableSuggestions} + final bool enableSuggestions; + + /// {@macro flutter.widgets.editableText.maxLines} + /// * [expands], which determines whether the field should fill the height of + /// its parent. + final int? maxLines; + + /// {@macro flutter.widgets.editableText.minLines} + /// * [expands], which determines whether the field should fill the height of + /// its parent. + final int? minLines; + + /// {@macro flutter.widgets.editableText.expands} + final bool expands; + + /// {@macro flutter.widgets.editableText.readOnly} + final bool readOnly; + + /// Configuration of toolbar options. + /// + /// If not set, select all and paste will default to be enabled. Copy and cut + /// will be disabled if [obscureText] is true. If [readOnly] is true, + /// paste and cut will be disabled regardless. + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + final ToolbarOptions? toolbarOptions; + + /// {@macro flutter.widgets.editableText.showCursor} + final bool? showCursor; + + /// If [maxLength] is set to this value, only the "current input length" + /// part of the character counter is shown. + static const int noMaxLength = -1; + + /// The maximum number of characters (Unicode grapheme clusters) to allow in + /// the text field. + /// + /// If set, a character counter will be displayed below the + /// field showing how many characters have been entered. If set to a number + /// greater than 0, it will also display the maximum number allowed. If set + /// to [TextField.noMaxLength] then only the current character count is displayed. + /// + /// After [maxLength] characters have been input, additional input + /// is ignored, unless [maxLengthEnforcement] is set to + /// [MaxLengthEnforcement.none]. + /// + /// The text field enforces the length with a [LengthLimitingTextInputFormatter], + /// which is evaluated after the supplied [inputFormatters], if any. + /// + /// This value must be either null, [TextField.noMaxLength], or greater than 0. + /// If null (the default) then there is no limit to the number of characters + /// that can be entered. If set to [TextField.noMaxLength], then no limit will + /// be enforced, but the number of characters entered will still be displayed. + /// + /// Whitespace characters (e.g. newline, space, tab) are included in the + /// character count. + /// + /// If [maxLengthEnforcement] is [MaxLengthEnforcement.none], then more than + /// [maxLength] characters may be entered, but the error counter and divider + /// will switch to the [decoration]'s [InputDecoration.errorStyle] when the + /// limit is exceeded. + /// + /// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength} + final int? maxLength; + + /// Determines how the [maxLength] limit should be enforced. + /// + /// {@macro flutter.services.textFormatter.effectiveMaxLengthEnforcement} + /// + /// {@macro flutter.services.textFormatter.maxLengthEnforcement} + final MaxLengthEnforcement? maxLengthEnforcement; + + /// {@macro flutter.widgets.editableText.onChanged} + /// + /// See also: + /// + /// * [inputFormatters], which are called before [onChanged] + /// runs and can validate and change ("format") the input value. + /// * [onEditingComplete], [onSubmitted]: + /// which are more specialized input change notifications. + final ValueChanged? onChanged; + + /// {@macro flutter.widgets.editableText.onEditingComplete} + final VoidCallback? onEditingComplete; + + /// {@macro flutter.widgets.editableText.onSubmitted} + /// + /// See also: + /// + /// * [TextInputAction.next] and [TextInputAction.previous], which + /// automatically shift the focus to the next/previous focusable item when + /// the user is done editing. + final ValueChanged? onSubmitted; + + /// {@macro flutter.widgets.editableText.onAppPrivateCommand} + final AppPrivateCommandCallback? onAppPrivateCommand; + + /// {@macro flutter.widgets.editableText.inputFormatters} + final List? inputFormatters; + + /// If false the text field is "disabled": it ignores taps and its + /// [decoration] is rendered in grey. + /// + /// If non-null this property overrides the [decoration]'s + /// [InputDecoration.enabled] property. + final bool? enabled; + + /// Determines whether this widget ignores pointer events. + /// + /// Defaults to null, and when null, does nothing. + final bool? ignorePointers; + + /// {@macro flutter.widgets.editableText.cursorWidth} + final double cursorWidth; + + /// {@macro flutter.widgets.editableText.cursorHeight} + final double? cursorHeight; + + /// {@macro flutter.widgets.editableText.cursorRadius} + final Radius? cursorRadius; + + /// {@macro flutter.widgets.editableText.cursorOpacityAnimates} + final bool? cursorOpacityAnimates; + + /// The color of the cursor. + /// + /// The cursor indicates the current location of text insertion point in + /// the field. + /// + /// If this is null it will default to the ambient + /// [DefaultSelectionStyle.cursorColor]. If that is null, and the + /// [ThemeData.platform] is [TargetPlatform.iOS] or [TargetPlatform.macOS] + /// it will use [CupertinoThemeData.primaryColor]. Otherwise it will use + /// the value of [ColorScheme.primary] of [ThemeData.colorScheme]. + final Color? cursorColor; + + /// The color of the cursor when the [InputDecorator] is showing an error. + /// + /// If this is null it will default to [TextStyle.color] of + /// [InputDecoration.errorStyle]. If that is null, it will use + /// [ColorScheme.error] of [ThemeData.colorScheme]. + final Color? cursorErrorColor; + + /// Controls how tall the selection highlight boxes are computed to be. + /// + /// See [ui.BoxHeightStyle] for details on available styles. + final ui.BoxHeightStyle selectionHeightStyle; + + /// Controls how wide the selection highlight boxes are computed to be. + /// + /// See [ui.BoxWidthStyle] for details on available styles. + final ui.BoxWidthStyle selectionWidthStyle; + + /// The appearance of the keyboard. + /// + /// This setting is only honored on iOS devices. + /// + /// If unset, defaults to [ThemeData.brightness]. + final Brightness? keyboardAppearance; + + /// {@macro flutter.widgets.editableText.scrollPadding} + final EdgeInsets scrollPadding; + + /// {@macro flutter.widgets.editableText.enableInteractiveSelection} + final bool enableInteractiveSelection; + + /// {@macro flutter.widgets.editableText.selectionControls} + final TextSelectionControls? selectionControls; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + /// {@macro flutter.widgets.editableText.selectionEnabled} + bool get selectionEnabled => enableInteractiveSelection; + + /// {@template flutter.material.textfield.onTap} + /// Called for the first tap in a series of taps. + /// + /// The text field builds a [GestureDetector] to handle input events like tap, + /// to trigger focus requests, to move the caret, adjust the selection, etc. + /// Handling some of those events by wrapping the text field with a competing + /// GestureDetector is problematic. + /// + /// To unconditionally handle taps, without interfering with the text field's + /// internal gesture detector, provide this callback. + /// + /// If the text field is created with [enabled] false, taps will not be + /// recognized. + /// + /// To be notified when the text field gains or loses the focus, provide a + /// [focusNode] and add a listener to that. + /// + /// To listen to arbitrary pointer events without competing with the + /// text field's internal gesture detector, use a [Listener]. + /// {@endtemplate} + /// + /// If [onTapAlwaysCalled] is enabled, this will also be called for consecutive + /// taps. + final GestureTapCallback? onTap; + + /// Whether [onTap] should be called for every tap. + /// + /// Defaults to false, so [onTap] is only called for each distinct tap. When + /// enabled, [onTap] is called for every tap including consecutive taps. + final bool onTapAlwaysCalled; + + /// {@macro flutter.widgets.editableText.onTapOutside} + /// + /// {@tool dartpad} + /// This example shows how to use a `TextFieldTapRegion` to wrap a set of + /// "spinner" buttons that increment and decrement a value in the [TextField] + /// without causing the text field to lose keyboard focus. + /// + /// This example includes a generic `SpinnerField` class that you can copy + /// into your own project and customize. + /// + /// ** See code in examples/api/lib/widgets/tap_region/text_field_tap_region.0.dart ** + /// {@end-tool} + /// + /// See also: + /// + /// * [TapRegion] for how the region group is determined. + final TapRegionCallback? onTapOutside; + + /// The cursor for a mouse pointer when it enters or is hovering over the + /// widget. + /// + /// If [mouseCursor] is a [MaterialStateProperty], + /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s: + /// + /// * [MaterialState.error]. + /// * [MaterialState.hovered]. + /// * [MaterialState.focused]. + /// * [MaterialState.disabled]. + /// + /// If this property is null, [MaterialStateMouseCursor.textable] will be used. + /// + /// The [mouseCursor] is the only property of [TextField] that controls the + /// appearance of the mouse pointer. All other properties related to "cursor" + /// stand for the text cursor, which is usually a blinking vertical line at + /// the editing position. + final MouseCursor? mouseCursor; + + /// Callback that generates a custom [InputDecoration.counter] widget. + /// + /// See [InputCounterWidgetBuilder] for an explanation of the passed in + /// arguments. The returned widget will be placed below the line in place of + /// the default widget built when [InputDecoration.counterText] is specified. + /// + /// The returned widget will be wrapped in a [Semantics] widget for + /// accessibility, but it also needs to be accessible itself. For example, + /// if returning a Text widget, set the [Text.semanticsLabel] property. + /// + /// {@tool snippet} + /// ```dart + /// Widget counter( + /// BuildContext context, + /// { + /// required int currentLength, + /// required int? maxLength, + /// required bool isFocused, + /// } + /// ) { + /// return Text( + /// '$currentLength of $maxLength characters', + /// semanticsLabel: 'character count', + /// ); + /// } + /// ``` + /// {@end-tool} + /// + /// If buildCounter returns null, then no counter and no Semantics widget will + /// be created at all. + final InputCounterWidgetBuilder? buildCounter; + + /// {@macro flutter.widgets.editableText.scrollPhysics} + final ScrollPhysics? scrollPhysics; + + /// {@macro flutter.widgets.editableText.scrollController} + final ScrollController? scrollController; + + /// {@macro flutter.widgets.editableText.autofillHints} + /// {@macro flutter.services.AutofillConfiguration.autofillHints} + final Iterable? autofillHints; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + + /// {@template flutter.material.textfield.restorationId} + /// Restoration ID to save and restore the state of the text field. + /// + /// If non-null, the text field will persist and restore its current scroll + /// offset and - if no [controller] has been provided - the content of the + /// text field. If a [controller] has been provided, it is the responsibility + /// of the owner of that controller to persist and restore it, e.g. by using + /// a [RestorableTextEditingController]. + /// + /// The state of this widget is persisted in a [RestorationBucket] claimed + /// from the surrounding [RestorationScope] using the provided restoration ID. + /// + /// See also: + /// + /// * [RestorationManager], which explains how state restoration works in + /// Flutter. + /// {@endtemplate} + final String? restorationId; + + /// {@macro flutter.widgets.editableText.scribbleEnabled} + final bool scribbleEnabled; + + /// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning} + final bool enableIMEPersonalizedLearning; + + /// {@macro flutter.widgets.editableText.contentInsertionConfiguration} + final ContentInsertionConfiguration? contentInsertionConfiguration; + + /// {@macro flutter.widgets.EditableText.contextMenuBuilder} + /// + /// If not provided, will build a default menu based on the platform. + /// + /// See also: + /// + /// * [AdaptiveTextSelectionToolbar], which is built by default. + final EditableTextContextMenuBuilder? contextMenuBuilder; + + /// Determine whether this text field can request the primary focus. + /// + /// Defaults to true. If false, the text field will not request focus + /// when tapped, or when its context menu is displayed. If false it will not + /// be possible to move the focus to the text field with tab key. + final bool canRequestFocus; + + /// {@macro flutter.widgets.undoHistory.controller} + final UndoHistoryController? undoController; + + static Widget _defaultContextMenuBuilder( + BuildContext context, EditableTextState editableTextState) { + return AdaptiveTextSelectionToolbar.editableText( + editableTextState: editableTextState, + ); + } + + /// {@macro flutter.widgets.EditableText.spellCheckConfiguration} + /// + /// If [SpellCheckConfiguration.misspelledTextStyle] is not specified in this + /// configuration, then [materialMisspelledTextStyle] is used by default. + final SpellCheckConfiguration? spellCheckConfiguration; + + /// The [TextStyle] used to indicate misspelled words in the Material style. + /// + /// See also: + /// * [_SpellCheckConfiguration.misspelledTextStyle], the style configured to + /// mark misspelled words with. + /// * [CupertinoTextField.cupertinoMisspelledTextStyle], the style configured + /// to mark misspelled words with in the Cupertino style. + static const TextStyle materialMisspelledTextStyle = TextStyle( + decoration: TextDecoration.underline, + decorationColor: Colors.red, + decorationStyle: TextDecorationStyle.wavy, + ); + + /// Default builder for [TextField]'s spell check suggestions toolbar. + /// + /// On Apple platforms, builds an iOS-style toolbar. Everywhere else, builds + /// an Android-style toolbar. + /// + /// See also: + /// * [spellCheckConfiguration], where this is typically specified for + /// [TextField]. + /// * [SpellCheckConfiguration.spellCheckSuggestionsToolbarBuilder], the + /// parameter for which this is the default value for [TextField]. + /// * [CupertinoTextField.defaultSpellCheckSuggestionsToolbarBuilder], which + /// is like this but specifies the default for [CupertinoTextField]. + @visibleForTesting + static Widget defaultSpellCheckSuggestionsToolbarBuilder( + BuildContext context, + EditableTextState editableTextState, + ) { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + return CupertinoSpellCheckSuggestionsToolbar.editableText( + editableTextState: editableTextState, + ); + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + return SpellCheckSuggestionsToolbar.editableText( + editableTextState: editableTextState, + ); + } + } + + /// Returns a new [SpellCheckConfiguration] where the given configuration has + /// had any missing values replaced with their defaults for the Android + /// platform. + static _SpellCheckConfiguration inferAndroidSpellCheckConfiguration( + _SpellCheckConfiguration? configuration, + ) { + if (configuration == null || + configuration == const _SpellCheckConfiguration.disabled()) { + return const _SpellCheckConfiguration.disabled(); + } + return configuration.copyWith( + misspelledTextStyle: configuration.misspelledTextStyle ?? + _TextField.materialMisspelledTextStyle, + spellCheckSuggestionsToolbarBuilder: + configuration.spellCheckSuggestionsToolbarBuilder ?? + _TextField.defaultSpellCheckSuggestionsToolbarBuilder, + ); + } + + @override + State<_TextField> createState() => _TextFieldState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty( + 'controller', controller, + defaultValue: null)); + properties.add(DiagnosticsProperty('focusNode', focusNode, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'undoController', undoController, + defaultValue: null)); + properties + .add(DiagnosticsProperty('enabled', enabled, defaultValue: null)); + properties.add(DiagnosticsProperty( + 'decoration', decoration, + defaultValue: const InputDecoration())); + properties.add(DiagnosticsProperty( + 'keyboardType', keyboardType, + defaultValue: TextInputType.text)); + properties.add( + DiagnosticsProperty('style', style, defaultValue: null)); + properties.add( + DiagnosticsProperty('autofocus', autofocus, defaultValue: false)); + properties.add(DiagnosticsProperty( + 'obscuringCharacter', obscuringCharacter, + defaultValue: '•')); + properties.add(DiagnosticsProperty('obscureText', obscureText, + defaultValue: false)); + properties.add(DiagnosticsProperty('autocorrect', autocorrect, + defaultValue: true)); + properties.add(EnumProperty( + 'smartDashesType', smartDashesType, + defaultValue: + obscureText ? SmartDashesType.disabled : SmartDashesType.enabled)); + properties.add(EnumProperty( + 'smartQuotesType', smartQuotesType, + defaultValue: + obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled)); + properties.add(DiagnosticsProperty( + 'enableSuggestions', enableSuggestions, + defaultValue: true)); + properties.add(IntProperty('maxLines', maxLines, defaultValue: 1)); + properties.add(IntProperty('minLines', minLines, defaultValue: null)); + properties.add( + DiagnosticsProperty('expands', expands, defaultValue: false)); + properties.add(IntProperty('maxLength', maxLength, defaultValue: null)); + properties.add(EnumProperty( + 'maxLengthEnforcement', maxLengthEnforcement, + defaultValue: null)); + properties.add(EnumProperty( + 'textInputAction', textInputAction, + defaultValue: null)); + properties.add(EnumProperty( + 'textCapitalization', textCapitalization, + defaultValue: TextCapitalization.none)); + properties.add(EnumProperty('textAlign', textAlign, + defaultValue: TextAlign.start)); + properties.add(DiagnosticsProperty( + 'textAlignVertical', textAlignVertical, + defaultValue: null)); + properties.add(EnumProperty('textDirection', textDirection, + defaultValue: null)); + properties + .add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0)); + properties + .add(DoubleProperty('cursorHeight', cursorHeight, defaultValue: null)); + properties.add(DiagnosticsProperty('cursorRadius', cursorRadius, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'cursorOpacityAnimates', cursorOpacityAnimates, + defaultValue: null)); + properties + .add(ColorProperty('cursorColor', cursorColor, defaultValue: null)); + properties.add(ColorProperty('cursorErrorColor', cursorErrorColor, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'keyboardAppearance', keyboardAppearance, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'scrollPadding', scrollPadding, + defaultValue: const EdgeInsets.all(20.0))); + properties.add(FlagProperty('selectionEnabled', + value: selectionEnabled, + defaultValue: true, + ifFalse: 'selection disabled')); + properties.add(DiagnosticsProperty( + 'selectionControls', selectionControls, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'scrollController', scrollController, + defaultValue: null)); + properties.add(DiagnosticsProperty( + 'scrollPhysics', scrollPhysics, + defaultValue: null)); + properties.add(DiagnosticsProperty('clipBehavior', clipBehavior, + defaultValue: Clip.hardEdge)); + properties.add(DiagnosticsProperty('scribbleEnabled', scribbleEnabled, + defaultValue: true)); + properties.add(DiagnosticsProperty( + 'enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, + defaultValue: true)); + properties.add(DiagnosticsProperty( + 'spellCheckConfiguration', spellCheckConfiguration, + defaultValue: null)); + properties.add(DiagnosticsProperty>('contentCommitMimeTypes', + contentInsertionConfiguration?.allowedMimeTypes ?? const [], + defaultValue: contentInsertionConfiguration == null + ? const [] + : kDefaultContentInsertionMimeTypes)); + } +} + +class _TextFieldState extends State<_TextField> + with RestorationMixin + implements _TextSelectionGestureDetectorBuilderDelegate, AutofillClient { + RestorableTextEditingController? _controller; + TextEditingController get _effectiveController => + widget.controller ?? _controller!.value; + + FocusNode? _focusNode; + FocusNode get _effectiveFocusNode => + widget.focusNode ?? (_focusNode ??= FocusNode()); + + MaxLengthEnforcement get _effectiveMaxLengthEnforcement => + widget.maxLengthEnforcement ?? + LengthLimitingTextInputFormatter.getDefaultMaxLengthEnforcement( + Theme.of(context).platform); + + bool _isHovering = false; + + bool get needsCounter => + widget.maxLength != null && + widget.decoration != null && + widget.decoration!.counterText == null; + + bool _showSelectionHandles = false; + + late _TextFieldSelectionGestureDetectorBuilder + _selectionGestureDetectorBuilder; + + // API for TextSelectionGestureDetectorBuilderDelegate. + @override + late bool forcePressEnabled; + + // zmtzawqlp + @override + final GlobalKey<_EditableTextState> editableTextKey = + GlobalKey<_EditableTextState>(); + + @override + bool get selectionEnabled => widget.selectionEnabled && _isEnabled; + // End of API for TextSelectionGestureDetectorBuilderDelegate. + + bool get _isEnabled => widget.enabled ?? widget.decoration?.enabled ?? true; + + int get _currentLength => _effectiveController.value.text.characters.length; + + bool get _hasIntrinsicError => + widget.maxLength != null && + widget.maxLength! > 0 && + (widget.controller == null + ? !restorePending && + _effectiveController.value.text.characters.length > + widget.maxLength! + : _effectiveController.value.text.characters.length > + widget.maxLength!); + + bool get _hasError => + widget.decoration?.errorText != null || + widget.decoration?.error != null || + _hasIntrinsicError; + + Color get _errorColor => + widget.cursorErrorColor ?? + _getEffectiveDecoration().errorStyle?.color ?? + Theme.of(context).colorScheme.error; + + InputDecoration _getEffectiveDecoration() { + final MaterialLocalizations localizations = + MaterialLocalizations.of(context); + final ThemeData themeData = Theme.of(context); + final InputDecoration effectiveDecoration = + (widget.decoration ?? const InputDecoration()) + .applyDefaults(themeData.inputDecorationTheme) + .copyWith( + enabled: _isEnabled, + hintMaxLines: widget.decoration?.hintMaxLines ?? widget.maxLines, + ); + + // No need to build anything if counter or counterText were given directly. + if (effectiveDecoration.counter != null || + effectiveDecoration.counterText != null) { + return effectiveDecoration; + } + + // If buildCounter was provided, use it to generate a counter widget. + Widget? counter; + final int currentLength = _currentLength; + if (effectiveDecoration.counter == null && + effectiveDecoration.counterText == null && + widget.buildCounter != null) { + final bool isFocused = _effectiveFocusNode.hasFocus; + final Widget? builtCounter = widget.buildCounter!( + context, + currentLength: currentLength, + maxLength: widget.maxLength, + isFocused: isFocused, + ); + // If buildCounter returns null, don't add a counter widget to the field. + if (builtCounter != null) { + counter = Semantics( + container: true, + liveRegion: isFocused, + child: builtCounter, + ); + } + return effectiveDecoration.copyWith(counter: counter); + } + + if (widget.maxLength == null) { + return effectiveDecoration; + } // No counter widget + + String counterText = '$currentLength'; + String semanticCounterText = ''; + + // Handle a real maxLength (positive number) + if (widget.maxLength! > 0) { + // Show the maxLength in the counter + counterText += '/${widget.maxLength}'; + final int remaining = + (widget.maxLength! - currentLength).clamp(0, widget.maxLength!); + semanticCounterText = + localizations.remainingTextFieldCharacterCount(remaining); + } + + if (_hasIntrinsicError) { + return effectiveDecoration.copyWith( + errorText: effectiveDecoration.errorText ?? '', + counterStyle: effectiveDecoration.errorStyle ?? + (themeData.useMaterial3 + ? _m3CounterErrorStyle(context) + : _m2CounterErrorStyle(context)), + counterText: counterText, + semanticCounterText: semanticCounterText, + ); + } + + return effectiveDecoration.copyWith( + counterText: counterText, + semanticCounterText: semanticCounterText, + ); + } + + @override + void initState() { + super.initState(); + _selectionGestureDetectorBuilder = + _TextFieldSelectionGestureDetectorBuilder(state: this); + if (widget.controller == null) { + _createLocalController(); + } + _effectiveFocusNode.canRequestFocus = widget.canRequestFocus && _isEnabled; + _effectiveFocusNode.addListener(_handleFocusChanged); + _initStatesController(); + } + + bool get _canRequestFocus { + final NavigationMode mode = + MediaQuery.maybeNavigationModeOf(context) ?? NavigationMode.traditional; + return switch (mode) { + NavigationMode.traditional => widget.canRequestFocus && _isEnabled, + NavigationMode.directional => true, + }; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _effectiveFocusNode.canRequestFocus = _canRequestFocus; + } + + @override + void didUpdateWidget(_TextField oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller == null && oldWidget.controller != null) { + _createLocalController(oldWidget.controller!.value); + } else if (widget.controller != null && oldWidget.controller == null) { + unregisterFromRestoration(_controller!); + _controller!.dispose(); + _controller = null; + } + + if (widget.focusNode != oldWidget.focusNode) { + (oldWidget.focusNode ?? _focusNode)?.removeListener(_handleFocusChanged); + (widget.focusNode ?? _focusNode)?.addListener(_handleFocusChanged); + } + + _effectiveFocusNode.canRequestFocus = _canRequestFocus; + + if (_effectiveFocusNode.hasFocus && + widget.readOnly != oldWidget.readOnly && + _isEnabled) { + if (_effectiveController.selection.isCollapsed) { + _showSelectionHandles = !widget.readOnly; + } + } + + if (widget.statesController == oldWidget.statesController) { + _statesController.update(MaterialState.disabled, !_isEnabled); + _statesController.update(MaterialState.hovered, _isHovering); + _statesController.update( + MaterialState.focused, _effectiveFocusNode.hasFocus); + _statesController.update(MaterialState.error, _hasError); + } else { + oldWidget.statesController?.removeListener(_handleStatesControllerChange); + if (widget.statesController != null) { + _internalStatesController?.dispose(); + _internalStatesController = null; + } + _initStatesController(); + } + } + + @override + void restoreState(RestorationBucket? oldBucket, bool initialRestore) { + if (_controller != null) { + _registerController(); + } + } + + void _registerController() { + assert(_controller != null); + registerForRestoration(_controller!, 'controller'); + } + + void _createLocalController([TextEditingValue? value]) { + assert(_controller == null); + _controller = value == null + ? RestorableTextEditingController() + : RestorableTextEditingController.fromValue(value); + if (!restorePending) { + _registerController(); + } + } + + @override + String? get restorationId => widget.restorationId; + + @override + void dispose() { + _effectiveFocusNode.removeListener(_handleFocusChanged); + _focusNode?.dispose(); + _controller?.dispose(); + _statesController.removeListener(_handleStatesControllerChange); + _internalStatesController?.dispose(); + super.dispose(); + } + + /// zmtzawqlp + _EditableTextState? get _editableText => editableTextKey.currentState; + + void _requestKeyboard() { + _editableText?.requestKeyboard(); + } + + bool _shouldShowSelectionHandles(SelectionChangedCause? cause) { + // When the text field is activated by something that doesn't trigger the + // selection overlay, we shouldn't show the handles either. + if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar) { + return false; + } + + if (cause == SelectionChangedCause.keyboard) { + return false; + } + + if (widget.readOnly && _effectiveController.selection.isCollapsed) { + return false; + } + + if (!_isEnabled) { + return false; + } + + if (cause == SelectionChangedCause.longPress || + cause == SelectionChangedCause.scribble) { + return true; + } + + if (_effectiveController.text.isNotEmpty) { + return true; + } + + return false; + } + + void _handleFocusChanged() { + setState(() { + // Rebuild the widget on focus change to show/hide the text selection + // highlight. + }); + _statesController.update( + MaterialState.focused, _effectiveFocusNode.hasFocus); + } + + void _handleSelectionChanged( + TextSelection selection, SelectionChangedCause? cause) { + final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause); + if (willShowSelectionHandles != _showSelectionHandles) { + setState(() { + _showSelectionHandles = willShowSelectionHandles; + }); + } + + switch (Theme.of(context).platform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + case TargetPlatform.linux: + case TargetPlatform.windows: + case TargetPlatform.fuchsia: + case TargetPlatform.android: + if (cause == SelectionChangedCause.longPress) { + _editableText?.bringIntoView(selection.extent); + } + } + + switch (Theme.of(context).platform) { + case TargetPlatform.iOS: + case TargetPlatform.fuchsia: + case TargetPlatform.android: + break; + case TargetPlatform.macOS: + case TargetPlatform.linux: + case TargetPlatform.windows: + if (cause == SelectionChangedCause.drag) { + _editableText?.hideToolbar(); + } + } + } + + /// Toggle the toolbar when a selection handle is tapped. + void _handleSelectionHandleTapped() { + if (_effectiveController.selection.isCollapsed) { + _editableText!.toggleToolbar(); + } + } + + void _handleHover(bool hovering) { + if (hovering != _isHovering) { + setState(() { + _isHovering = hovering; + }); + _statesController.update(MaterialState.hovered, _isHovering); + } + } + + // Material states controller. + MaterialStatesController? _internalStatesController; + + void _handleStatesControllerChange() { + // Force a rebuild to resolve MaterialStateProperty properties. + setState(() {}); + } + + MaterialStatesController get _statesController => + widget.statesController ?? _internalStatesController!; + + void _initStatesController() { + if (widget.statesController == null) { + _internalStatesController = MaterialStatesController(); + } + _statesController.update(MaterialState.disabled, !_isEnabled); + _statesController.update(MaterialState.hovered, _isHovering); + _statesController.update( + MaterialState.focused, _effectiveFocusNode.hasFocus); + _statesController.update(MaterialState.error, _hasError); + _statesController.addListener(_handleStatesControllerChange); + } + + // AutofillClient implementation start. + @override + String get autofillId => _editableText!.autofillId; + + @override + void autofill(TextEditingValue newEditingValue) => + _editableText!.autofill(newEditingValue); + + @override + TextInputConfiguration get textInputConfiguration { + final List? autofillHints = + widget.autofillHints?.toList(growable: false); + final AutofillConfiguration autofillConfiguration = autofillHints != null + ? AutofillConfiguration( + uniqueIdentifier: autofillId, + autofillHints: autofillHints, + currentEditingValue: _effectiveController.value, + hintText: (widget.decoration ?? const InputDecoration()).hintText, + ) + : AutofillConfiguration.disabled; + + return _editableText!.textInputConfiguration + .copyWith(autofillConfiguration: autofillConfiguration); + } + // AutofillClient implementation end. + + TextStyle _getInputStyleForState(TextStyle style) { + final ThemeData theme = Theme.of(context); + final TextStyle stateStyle = MaterialStateProperty.resolveAs( + theme.useMaterial3 + ? _m3StateInputStyle(context)! + : _m2StateInputStyle(context)!, + _statesController.value); + final TextStyle providedStyle = + MaterialStateProperty.resolveAs(style, _statesController.value); + return providedStyle.merge(stateStyle); + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMaterial(context)); + assert(debugCheckHasMaterialLocalizations(context)); + assert(debugCheckHasDirectionality(context)); + assert( + !(widget.style != null && + !widget.style!.inherit && + (widget.style!.fontSize == null || + widget.style!.textBaseline == null)), + 'inherit false style must supply fontSize and textBaseline', + ); + + final ThemeData theme = Theme.of(context); + final DefaultSelectionStyle selectionStyle = + DefaultSelectionStyle.of(context); + final TextStyle? providedStyle = + MaterialStateProperty.resolveAs(widget.style, _statesController.value); + final TextStyle style = _getInputStyleForState(theme.useMaterial3 + ? _m3InputStyle(context) + : theme.textTheme.titleMedium!) + .merge(providedStyle); + final Brightness keyboardAppearance = + widget.keyboardAppearance ?? theme.brightness; + final TextEditingController controller = _effectiveController; + final FocusNode focusNode = _effectiveFocusNode; + final List formatters = [ + ...?widget.inputFormatters, + if (widget.maxLength != null) + LengthLimitingTextInputFormatter( + widget.maxLength, + maxLengthEnforcement: _effectiveMaxLengthEnforcement, + ), + ]; + + // Set configuration as disabled if not otherwise specified. If specified, + // ensure that configuration uses the correct style for misspelled words for + // the current platform, unless a custom style is specified. + final SpellCheckConfiguration spellCheckConfiguration; + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + spellCheckConfiguration = + CupertinoTextField.inferIOSSpellCheckConfiguration( + widget.spellCheckConfiguration, + ); + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + spellCheckConfiguration = TextField.inferAndroidSpellCheckConfiguration( + widget.spellCheckConfiguration, + ); + } + + TextSelectionControls? textSelectionControls = widget.selectionControls; + final bool paintCursorAboveText; + bool? cursorOpacityAnimates = widget.cursorOpacityAnimates; + Offset? cursorOffset; + final Color cursorColor; + final Color selectionColor; + Color? autocorrectionTextRectColor; + Radius? cursorRadius = widget.cursorRadius; + VoidCallback? handleDidGainAccessibilityFocus; + VoidCallback? handleDidLoseAccessibilityFocus; + + switch (theme.platform) { + case TargetPlatform.iOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + forcePressEnabled = true; + textSelectionControls ??= cupertinoTextSelectionHandleControls; + paintCursorAboveText = true; + cursorOpacityAnimates ??= true; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + cupertinoTheme.primaryColor; + selectionColor = selectionStyle.selectionColor ?? + cupertinoTheme.primaryColor.withOpacity(0.40); + cursorRadius ??= const Radius.circular(2.0); + cursorOffset = Offset( + iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0); + autocorrectionTextRectColor = selectionColor; + + case TargetPlatform.macOS: + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); + forcePressEnabled = false; + textSelectionControls ??= cupertinoDesktopTextSelectionHandleControls; + paintCursorAboveText = true; + cursorOpacityAnimates ??= false; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + cupertinoTheme.primaryColor; + selectionColor = selectionStyle.selectionColor ?? + cupertinoTheme.primaryColor.withOpacity(0.40); + cursorRadius ??= const Radius.circular(2.0); + cursorOffset = Offset( + iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0); + handleDidGainAccessibilityFocus = () { + // Automatically activate the TextField when it receives accessibility focus. + if (!_effectiveFocusNode.hasFocus && + _effectiveFocusNode.canRequestFocus) { + _effectiveFocusNode.requestFocus(); + } + }; + handleDidLoseAccessibilityFocus = () { + _effectiveFocusNode.unfocus(); + }; + + case TargetPlatform.android: + case TargetPlatform.fuchsia: + forcePressEnabled = false; + textSelectionControls ??= materialTextSelectionHandleControls; + paintCursorAboveText = false; + cursorOpacityAnimates ??= false; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + theme.colorScheme.primary; + selectionColor = selectionStyle.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + + case TargetPlatform.linux: + forcePressEnabled = false; + textSelectionControls ??= desktopTextSelectionHandleControls; + paintCursorAboveText = false; + cursorOpacityAnimates ??= false; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + theme.colorScheme.primary; + selectionColor = selectionStyle.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + handleDidGainAccessibilityFocus = () { + // Automatically activate the TextField when it receives accessibility focus. + if (!_effectiveFocusNode.hasFocus && + _effectiveFocusNode.canRequestFocus) { + _effectiveFocusNode.requestFocus(); + } + }; + handleDidLoseAccessibilityFocus = () { + _effectiveFocusNode.unfocus(); + }; + + case TargetPlatform.windows: + forcePressEnabled = false; + textSelectionControls ??= desktopTextSelectionHandleControls; + paintCursorAboveText = false; + cursorOpacityAnimates ??= false; + cursorColor = _hasError + ? _errorColor + : widget.cursorColor ?? + selectionStyle.cursorColor ?? + theme.colorScheme.primary; + selectionColor = selectionStyle.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + handleDidGainAccessibilityFocus = () { + // Automatically activate the TextField when it receives accessibility focus. + if (!_effectiveFocusNode.hasFocus && + _effectiveFocusNode.canRequestFocus) { + _effectiveFocusNode.requestFocus(); + } + }; + handleDidLoseAccessibilityFocus = () { + _effectiveFocusNode.unfocus(); + }; + } + + Widget child = RepaintBoundary( + child: UnmanagedRestorationScope( + bucket: bucket, + child: EditableText( + key: editableTextKey, + readOnly: widget.readOnly || !_isEnabled, + toolbarOptions: widget.toolbarOptions, + showCursor: widget.showCursor, + showSelectionHandles: _showSelectionHandles, + controller: controller, + focusNode: focusNode, + undoController: widget.undoController, + keyboardType: widget.keyboardType, + textInputAction: widget.textInputAction, + textCapitalization: widget.textCapitalization, + style: style, + strutStyle: widget.strutStyle, + textAlign: widget.textAlign, + textDirection: widget.textDirection, + autofocus: widget.autofocus, + obscuringCharacter: widget.obscuringCharacter, + obscureText: widget.obscureText, + autocorrect: widget.autocorrect, + smartDashesType: widget.smartDashesType, + smartQuotesType: widget.smartQuotesType, + enableSuggestions: widget.enableSuggestions, + maxLines: widget.maxLines, + minLines: widget.minLines, + expands: widget.expands, + // Only show the selection highlight when the text field is focused. + selectionColor: focusNode.hasFocus ? selectionColor : null, + selectionControls: + widget.selectionEnabled ? textSelectionControls : null, + onChanged: widget.onChanged, + onSelectionChanged: _handleSelectionChanged, + onEditingComplete: widget.onEditingComplete, + onSubmitted: widget.onSubmitted, + onAppPrivateCommand: widget.onAppPrivateCommand, + groupId: widget.groupId, + onSelectionHandleTapped: _handleSelectionHandleTapped, + onTapOutside: widget.onTapOutside, + inputFormatters: formatters, + rendererIgnoresPointer: true, + mouseCursor: MouseCursor.defer, // TextField will handle the cursor + cursorWidth: widget.cursorWidth, + cursorHeight: widget.cursorHeight, + cursorRadius: cursorRadius, + cursorColor: cursorColor, + selectionHeightStyle: widget.selectionHeightStyle, + selectionWidthStyle: widget.selectionWidthStyle, + cursorOpacityAnimates: cursorOpacityAnimates, + cursorOffset: cursorOffset, + paintCursorAboveText: paintCursorAboveText, + backgroundCursorColor: CupertinoColors.inactiveGray, + scrollPadding: widget.scrollPadding, + keyboardAppearance: keyboardAppearance, + enableInteractiveSelection: widget.enableInteractiveSelection, + dragStartBehavior: widget.dragStartBehavior, + scrollController: widget.scrollController, + scrollPhysics: widget.scrollPhysics, + autofillClient: this, + autocorrectionTextRectColor: autocorrectionTextRectColor, + clipBehavior: widget.clipBehavior, + restorationId: 'editable', + scribbleEnabled: widget.scribbleEnabled, + enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, + contentInsertionConfiguration: widget.contentInsertionConfiguration, + contextMenuBuilder: widget.contextMenuBuilder, + spellCheckConfiguration: spellCheckConfiguration, + magnifierConfiguration: widget.magnifierConfiguration ?? + TextMagnifier.adaptiveMagnifierConfiguration, + ), + ), + ); + + if (widget.decoration != null) { + child = AnimatedBuilder( + animation: Listenable.merge([focusNode, controller]), + builder: (BuildContext context, Widget? child) { + return InputDecorator( + decoration: _getEffectiveDecoration(), + baseStyle: widget.style, + textAlign: widget.textAlign, + textAlignVertical: widget.textAlignVertical, + isHovering: _isHovering, + isFocused: focusNode.hasFocus, + isEmpty: controller.value.text.isEmpty, + expands: widget.expands, + child: child, + ); + }, + child: child, + ); + } + final MouseCursor effectiveMouseCursor = + MaterialStateProperty.resolveAs( + widget.mouseCursor ?? MaterialStateMouseCursor.textable, + _statesController.value, + ); + + final int? semanticsMaxValueLength; + if (_effectiveMaxLengthEnforcement != MaxLengthEnforcement.none && + widget.maxLength != null && + widget.maxLength! > 0) { + semanticsMaxValueLength = widget.maxLength; + } else { + semanticsMaxValueLength = null; + } + + return MouseRegion( + cursor: effectiveMouseCursor, + onEnter: (PointerEnterEvent event) => _handleHover(true), + onExit: (PointerExitEvent event) => _handleHover(false), + child: TextFieldTapRegion( + child: IgnorePointer( + ignoring: widget.ignorePointers ?? !_isEnabled, + child: AnimatedBuilder( + animation: controller, // changes the _currentLength + builder: (BuildContext context, Widget? child) { + return Semantics( + enabled: _isEnabled, + maxValueLength: semanticsMaxValueLength, + currentValueLength: _currentLength, + onTap: widget.readOnly + ? null + : () { + if (!_effectiveController.selection.isValid) { + _effectiveController.selection = + TextSelection.collapsed( + offset: _effectiveController.text.length); + } + _requestKeyboard(); + }, + onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus, + onDidLoseAccessibilityFocus: handleDidLoseAccessibilityFocus, + onFocus: _isEnabled + ? () { + assert( + _effectiveFocusNode.canRequestFocus, + 'Received SemanticsAction.focus from the engine. However, the FocusNode ' + 'of this text field cannot gain focus. This likely indicates a bug. ' + 'If this text field cannot be focused (e.g. because it is not ' + 'enabled), then its corresponding semantics node must be configured ' + 'such that the assistive technology cannot request focus on it.'); + + if (_effectiveFocusNode.canRequestFocus && + !_effectiveFocusNode.hasFocus) { + _effectiveFocusNode.requestFocus(); + } else if (!widget.readOnly) { + // If the platform requested focus, that means that previously the + // platform believed that the text field did not have focus (even + // though Flutter's widget system believed otherwise). This likely + // means that the on-screen keyboard is hidden, or more generally, + // there is no current editing session in this field. To correct + // that, keyboard must be requested. + // + // A concrete scenario where this can happen is when the user + // dismisses the keyboard on the web. The editing session is + // closed by the engine, but the text field widget stays focused + // in the framework. + _requestKeyboard(); + } + } + : null, + child: child, + ); + }, + child: _selectionGestureDetectorBuilder.buildGestureDetector( + behavior: HitTestBehavior.translucent, + child: child, + ), + ), + ), + ), + ); + } +} + +TextStyle? _m2StateInputStyle(BuildContext context) => + MaterialStateTextStyle.resolveWith((Set states) { + final ThemeData theme = Theme.of(context); + if (states.contains(MaterialState.disabled)) { + return TextStyle(color: theme.disabledColor); + } + return TextStyle(color: theme.textTheme.titleMedium?.color); + }); + +TextStyle _m2CounterErrorStyle(BuildContext context) => Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Theme.of(context).colorScheme.error); + +// BEGIN GENERATED TOKEN PROPERTIES - TextField + +// Do not edit by hand. The code between the "BEGIN GENERATED" and +// "END GENERATED" comments are generated from data in the Material +// Design token database by the script: +// dev/tools/gen_defaults/bin/gen_defaults.dart. + +TextStyle? _m3StateInputStyle(BuildContext context) => + MaterialStateTextStyle.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return TextStyle( + color: Theme.of(context) + .textTheme + .bodyLarge! + .color + ?.withOpacity(0.38)); + } + return TextStyle(color: Theme.of(context).textTheme.bodyLarge!.color); + }); + +TextStyle _m3InputStyle(BuildContext context) => + Theme.of(context).textTheme.bodyLarge!; + +TextStyle _m3CounterErrorStyle(BuildContext context) => Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Theme.of(context).colorScheme.error); + +// END GENERATED TOKEN PROPERTIES - TextField diff --git a/local_packages/extended_text_field-16.0.2/lib/src/official/widgets/text_selection.dart b/local_packages/extended_text_field-16.0.2/lib/src/official/widgets/text_selection.dart new file mode 100644 index 0000000..fc02e60 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/lib/src/official/widgets/text_selection.dart @@ -0,0 +1,2996 @@ +part of 'package:extended_text_field/src/extended/widgets/text_field.dart'; + +/// [TextSelectionOverlay] +/// An object that manages a pair of text selection handles for a +/// [RenderEditable]. +/// +/// This class is a wrapper of [SelectionOverlay] to provide APIs specific for +/// [RenderEditable]s. To manage selection handles for custom widgets, use +/// [SelectionOverlay] instead. +class _TextSelectionOverlay { + /// Creates an object that manages overlay entries for selection handles. + /// + /// The [context] must have an [Overlay] as an ancestor. + _TextSelectionOverlay({ + required TextEditingValue value, + required this.context, + Widget? debugRequiredFor, + required LayerLink toolbarLayerLink, + required LayerLink startHandleLayerLink, + required LayerLink endHandleLayerLink, + required this.renderObject, + this.selectionControls, + bool handlesVisible = false, + required this.selectionDelegate, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, + VoidCallback? onSelectionHandleTapped, + ClipboardStatusNotifier? clipboardStatus, + this.contextMenuBuilder, + required TextMagnifierConfiguration magnifierConfiguration, + }) : _handlesVisible = handlesVisible, + _value = value { + // TODO(polina-c): stop duplicating code across disposables + // https://github.com/flutter/flutter/issues/137435 + if (kFlutterMemoryAllocationsEnabled) { + FlutterMemoryAllocations.instance.dispatchObjectCreated( + library: 'package:flutter/widgets.dart', + className: '$TextSelectionOverlay', + object: this, + ); + } + renderObject.selectionStartInViewport + .addListener(_updateTextSelectionOverlayVisibilities); + renderObject.selectionEndInViewport + .addListener(_updateTextSelectionOverlayVisibilities); + _updateTextSelectionOverlayVisibilities(); + _selectionOverlay = _SelectionOverlay( + magnifierConfiguration: magnifierConfiguration, + context: context, + debugRequiredFor: debugRequiredFor, + // The metrics will be set when show handles. + startHandleType: TextSelectionHandleType.collapsed, + startHandlesVisible: _effectiveStartHandleVisibility, + lineHeightAtStart: 0.0, + onStartHandleDragStart: _handleSelectionStartHandleDragStart, + onStartHandleDragUpdate: _handleSelectionStartHandleDragUpdate, + onEndHandleDragEnd: _handleAnyDragEnd, + endHandleType: TextSelectionHandleType.collapsed, + endHandlesVisible: _effectiveEndHandleVisibility, + lineHeightAtEnd: 0.0, + onEndHandleDragStart: _handleSelectionEndHandleDragStart, + onEndHandleDragUpdate: _handleSelectionEndHandleDragUpdate, + onStartHandleDragEnd: _handleAnyDragEnd, + toolbarVisible: _effectiveToolbarVisibility, + selectionEndpoints: const [], + selectionControls: selectionControls, + selectionDelegate: selectionDelegate, + clipboardStatus: clipboardStatus, + startHandleLayerLink: startHandleLayerLink, + endHandleLayerLink: endHandleLayerLink, + toolbarLayerLink: toolbarLayerLink, + onSelectionHandleTapped: onSelectionHandleTapped, + dragStartBehavior: dragStartBehavior, + toolbarLocation: renderObject.lastSecondaryTapDownPosition, + ); + } + + /// {@template flutter.widgets.SelectionOverlay.context} + /// The context in which the selection UI should appear. + /// + /// This context must have an [Overlay] as an ancestor because this object + /// will display the text selection handles in that [Overlay]. + /// {@endtemplate} + final BuildContext context; + + // TODO(mpcomplete): what if the renderObject is removed or replaced, or + // moves? Not sure what cases I need to handle, or how to handle them. + /// The editable line in which the selected text is being displayed. + /// zmtzawqlp + final _RenderEditable renderObject; + + /// {@macro flutter.widgets.SelectionOverlay.selectionControls} + final TextSelectionControls? selectionControls; + + /// {@macro flutter.widgets.SelectionOverlay.selectionDelegate} + final TextSelectionDelegate selectionDelegate; + + /// zmtzawqlp + late final _SelectionOverlay _selectionOverlay; + + /// {@macro flutter.widgets.EditableText.contextMenuBuilder} + /// + /// If not provided, no context menu will be built. + final WidgetBuilder? contextMenuBuilder; + + /// Retrieve current value. + @visibleForTesting + TextEditingValue get value => _value; + + TextEditingValue _value; + + TextSelection get _selection => _value.selection; + + final ValueNotifier _effectiveStartHandleVisibility = + ValueNotifier(false); + final ValueNotifier _effectiveEndHandleVisibility = + ValueNotifier(false); + final ValueNotifier _effectiveToolbarVisibility = + ValueNotifier(false); + + void _updateTextSelectionOverlayVisibilities() { + _effectiveStartHandleVisibility.value = + _handlesVisible && renderObject.selectionStartInViewport.value; + _effectiveEndHandleVisibility.value = + _handlesVisible && renderObject.selectionEndInViewport.value; + _effectiveToolbarVisibility.value = + renderObject.selectionStartInViewport.value || + renderObject.selectionEndInViewport.value; + } + + /// Whether selection handles are visible. + /// + /// Set to false if you want to hide the handles. Use this property to show or + /// hide the handle without rebuilding them. + /// + /// Defaults to false. + bool get handlesVisible => _handlesVisible; + bool _handlesVisible = false; + set handlesVisible(bool visible) { + if (_handlesVisible == visible) { + return; + } + _handlesVisible = visible; + _updateTextSelectionOverlayVisibilities(); + } + + /// {@macro flutter.widgets.SelectionOverlay.showHandles} + void showHandles() { + _updateSelectionOverlay(); + _selectionOverlay.showHandles(); + } + + /// {@macro flutter.widgets.SelectionOverlay.hideHandles} + void hideHandles() => _selectionOverlay.hideHandles(); + + /// {@macro flutter.widgets.SelectionOverlay.showToolbar} + void showToolbar() { + _updateSelectionOverlay(); + + if (selectionControls != null && + selectionControls is! TextSelectionHandleControls) { + _selectionOverlay.showToolbar(); + return; + } + + if (contextMenuBuilder == null) { + return; + } + + assert(context.mounted); + _selectionOverlay.showToolbar( + context: context, + contextMenuBuilder: contextMenuBuilder, + ); + return; + } + + /// Shows toolbar with spell check suggestions of misspelled words that are + /// available for click-and-replace. + void showSpellCheckSuggestionsToolbar( + WidgetBuilder spellCheckSuggestionsToolbarBuilder) { + _updateSelectionOverlay(); + assert(context.mounted); + _selectionOverlay.showSpellCheckSuggestionsToolbar( + context: context, + builder: spellCheckSuggestionsToolbarBuilder, + ); + hideHandles(); + } + + /// {@macro flutter.widgets.SelectionOverlay.showMagnifier} + void showMagnifier(Offset positionToShow) { + final TextPosition position = + renderObject.getPositionForPoint(positionToShow); + _updateSelectionOverlay(); + _selectionOverlay.showMagnifier( + _buildMagnifier( + currentTextPosition: position, + globalGesturePosition: positionToShow, + renderEditable: renderObject, + ), + ); + } + + /// {@macro flutter.widgets.SelectionOverlay.updateMagnifier} + void updateMagnifier(Offset positionToShow) { + final TextPosition position = + renderObject.getPositionForPoint(positionToShow); + _updateSelectionOverlay(); + _selectionOverlay.updateMagnifier( + _buildMagnifier( + currentTextPosition: position, + globalGesturePosition: positionToShow, + renderEditable: renderObject, + ), + ); + } + + /// {@macro flutter.widgets.SelectionOverlay.hideMagnifier} + void hideMagnifier() { + _selectionOverlay.hideMagnifier(); + } + + /// Updates the overlay after the selection has changed. + /// + /// If this method is called while the [SchedulerBinding.schedulerPhase] is + /// [SchedulerPhase.persistentCallbacks], i.e. during the build, layout, or + /// paint phases (see [WidgetsBinding.drawFrame]), then the update is delayed + /// until the post-frame callbacks phase. Otherwise the update is done + /// synchronously. This means that it is safe to call during builds, but also + /// that if you do call this during a build, the UI will not update until the + /// next frame (i.e. many milliseconds later). + void update(TextEditingValue newValue) { + if (_value == newValue) { + return; + } + _value = newValue; + _updateSelectionOverlay(); + // _updateSelectionOverlay may not rebuild the selection overlay if the + // text metrics and selection doesn't change even if the text has changed. + // This rebuild is needed for the toolbar to update based on the latest text + // value. + _selectionOverlay.markNeedsBuild(); + } + + void _updateSelectionOverlay() { + _selectionOverlay + // Update selection handle metrics. + ..startHandleType = _chooseType( + renderObject.textDirection, + TextSelectionHandleType.left, + TextSelectionHandleType.right, + ) + ..lineHeightAtStart = _getStartGlyphHeight() + ..endHandleType = _chooseType( + renderObject.textDirection, + TextSelectionHandleType.right, + TextSelectionHandleType.left, + ) + ..lineHeightAtEnd = _getEndGlyphHeight() + // Update selection toolbar metrics. + ..selectionEndpoints = renderObject.getEndpointsForSelection(_selection) + ..toolbarLocation = renderObject.lastSecondaryTapDownPosition; + } + + /// Causes the overlay to update its rendering. + /// + /// This is intended to be called when the [renderObject] may have changed its + /// text metrics (e.g. because the text was scrolled). + void updateForScroll() { + _updateSelectionOverlay(); + // This method may be called due to windows metrics changes. In that case, + // non of the properties in _selectionOverlay will change, but a rebuild is + // still needed. + _selectionOverlay.markNeedsBuild(); + } + + /// Whether the handles are currently visible. + bool get handlesAreVisible => + _selectionOverlay._handles != null && handlesVisible; + + /// {@macro flutter.widgets.SelectionOverlay.toolbarIsVisible} + /// + /// See also: + /// + /// * [spellCheckToolbarIsVisible], which is only whether the spell check menu + /// specifically is visible. + bool get toolbarIsVisible => _selectionOverlay.toolbarIsVisible; + + /// Whether the magnifier is currently visible. + bool get magnifierIsVisible => _selectionOverlay._magnifierController.shown; + + /// Whether the spell check menu is currently visible. + /// + /// See also: + /// + /// * [toolbarIsVisible], which is whether any toolbar is visible. + bool get spellCheckToolbarIsVisible => + _selectionOverlay._spellCheckToolbarController.isShown; + + /// {@macro flutter.widgets.SelectionOverlay.hide} + void hide() => _selectionOverlay.hide(); + + /// {@macro flutter.widgets.SelectionOverlay.hideToolbar} + void hideToolbar() => _selectionOverlay.hideToolbar(); + + /// {@macro flutter.widgets.SelectionOverlay.dispose} + void dispose() { + // TODO(polina-c): stop duplicating code across disposables + // https://github.com/flutter/flutter/issues/137435 + if (kFlutterMemoryAllocationsEnabled) { + FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this); + } + _selectionOverlay.dispose(); + renderObject.selectionStartInViewport + .removeListener(_updateTextSelectionOverlayVisibilities); + renderObject.selectionEndInViewport + .removeListener(_updateTextSelectionOverlayVisibilities); + _effectiveToolbarVisibility.dispose(); + _effectiveStartHandleVisibility.dispose(); + _effectiveEndHandleVisibility.dispose(); + hideToolbar(); + } + + double _getStartGlyphHeight() { + final String currText = selectionDelegate.textEditingValue.text; + final int firstSelectedGraphemeExtent; + Rect? startHandleRect; + // Only calculate handle rects if the text in the previous frame + // is the same as the text in the current frame. This is done because + // widget.renderObject contains the renderEditable from the previous frame. + // If the text changed between the current and previous frames then + // widget.renderObject.getRectForComposingRange might fail. In cases where + // the current frame is different from the previous we fall back to + // renderObject.preferredLineHeight. + if (renderObject.plainText == currText && + _selection.isValid && + !_selection.isCollapsed) { + final String selectedGraphemes = _selection.textInside(currText); + firstSelectedGraphemeExtent = selectedGraphemes.characters.first.length; + startHandleRect = renderObject.getRectForComposingRange(TextRange( + start: _selection.start, + end: _selection.start + firstSelectedGraphemeExtent)); + } + return startHandleRect?.height ?? renderObject.preferredLineHeight; + } + + double _getEndGlyphHeight() { + final String currText = selectionDelegate.textEditingValue.text; + final int lastSelectedGraphemeExtent; + Rect? endHandleRect; + // See the explanation in _getStartGlyphHeight. + if (renderObject.plainText == currText && + _selection.isValid && + !_selection.isCollapsed) { + final String selectedGraphemes = _selection.textInside(currText); + lastSelectedGraphemeExtent = selectedGraphemes.characters.last.length; + endHandleRect = renderObject.getRectForComposingRange(TextRange( + start: _selection.end - lastSelectedGraphemeExtent, + end: _selection.end)); + } + return endHandleRect?.height ?? renderObject.preferredLineHeight; + } + + MagnifierInfo _buildMagnifier({ + // zmtzawqlp + required _RenderEditable renderEditable, + required Offset globalGesturePosition, + required TextPosition currentTextPosition, + }) { + final TextSelection lineAtOffset = + renderEditable.getLineAtOffset(currentTextPosition); + + final TextPosition positionAtEndOfLine = TextPosition( + offset: lineAtOffset.extentOffset, + affinity: TextAffinity.upstream, + ); + + // Default affinity is downstream. + final TextPosition positionAtBeginningOfLine = TextPosition( + offset: lineAtOffset.baseOffset, + ); + + final Rect localLineBoundaries = Rect.fromPoints( + renderEditable.getLocalRectForCaret(positionAtBeginningOfLine).topCenter, + renderEditable.getLocalRectForCaret(positionAtEndOfLine).bottomCenter, + ); + final RenderBox? overlay = Overlay.of(context, rootOverlay: true) + .context + .findRenderObject() as RenderBox?; + final Matrix4 transformToOverlay = renderEditable.getTransformTo(overlay); + final Rect overlayLineBoundaries = MatrixUtils.transformRect( + transformToOverlay, + localLineBoundaries, + ); + + final Rect localCaretRect = + renderEditable.getLocalRectForCaret(currentTextPosition); + final Rect overlayCaretRect = MatrixUtils.transformRect( + transformToOverlay, + localCaretRect, + ); + + final Offset overlayGesturePosition = + overlay?.globalToLocal(globalGesturePosition) ?? globalGesturePosition; + + return MagnifierInfo( + fieldBounds: MatrixUtils.transformRect( + transformToOverlay, renderEditable.paintBounds), + globalGesturePosition: overlayGesturePosition, + caretRect: overlayCaretRect, + currentLineBoundaries: overlayLineBoundaries, + ); + } + + // The contact position of the gesture at the current end handle location, in + // global coordinates. Updated when the handle moves. + late double _endHandleDragPosition; + + // The distance from _endHandleDragPosition to the center of the line that it + // corresponds to, in global coordinates. + late double _endHandleDragTarget; + + void _handleSelectionEndHandleDragStart(DragStartDetails details) { + if (!renderObject.attached) { + return; + } + + _endHandleDragPosition = details.globalPosition.dy; + + // Use local coordinates when dealing with line height. because in case of a + // scale transformation, the line height will also be scaled. + final double centerOfLineLocal = + _selectionOverlay.selectionEndpoints.last.point.dy - + renderObject.preferredLineHeight / 2; + final double centerOfLineGlobal = renderObject + .localToGlobal( + Offset(0.0, centerOfLineLocal), + ) + .dy; + _endHandleDragTarget = centerOfLineGlobal - details.globalPosition.dy; + // Instead of finding the TextPosition at the handle's location directly, + // use the vertical center of the line that it points to. This is because + // selection handles typically hang above or below the line that they point + // to. + final TextPosition position = renderObject.getPositionForPoint( + Offset( + details.globalPosition.dx, + centerOfLineGlobal, + ), + ); + + _selectionOverlay.showMagnifier( + _buildMagnifier( + currentTextPosition: position, + globalGesturePosition: details.globalPosition, + renderEditable: renderObject, + ), + ); + } + + /// Given a handle position and drag position, returns the position of handle + /// after the drag. + /// + /// The handle jumps instantly between lines when the drag reaches a full + /// line's height away from the original handle position. In other words, the + /// line jump happens when the contact point would be located at the same + /// place on the handle at the new line as when the gesture started, for both + /// directions. + /// + /// This is not the same as just maintaining an offset from the target and the + /// contact point. There is no point at which moving the drag up and down a + /// small sub-line-height distance will cause the cursor to jump up and down + /// between lines. The drag distance must be a full line height for the cursor + /// to change lines, for both directions. + /// + /// Both parameters must be in local coordinates because the untransformed + /// line height is used, and the return value is in local coordinates as well. + double _getHandleDy(double dragDy, double handleDy) { + final double distanceDragged = dragDy - handleDy; + final int dragDirection = distanceDragged < 0.0 ? -1 : 1; + final int linesDragged = dragDirection * + (distanceDragged.abs() / renderObject.preferredLineHeight).floor(); + return handleDy + linesDragged * renderObject.preferredLineHeight; + } + + void _handleSelectionEndHandleDragUpdate(DragUpdateDetails details) { + if (!renderObject.attached) { + return; + } + + // This is NOT the same as details.localPosition. That is relative to the + // selection handle, whereas this is relative to the RenderEditable. + final Offset localPosition = + renderObject.globalToLocal(details.globalPosition); + + final double nextEndHandleDragPositionLocal = _getHandleDy( + localPosition.dy, + renderObject.globalToLocal(Offset(0.0, _endHandleDragPosition)).dy, + ); + _endHandleDragPosition = renderObject + .localToGlobal( + Offset(0.0, nextEndHandleDragPositionLocal), + ) + .dy; + + final Offset handleTargetGlobal = Offset( + details.globalPosition.dx, + _endHandleDragPosition + _endHandleDragTarget, + ); + + final TextPosition position = + renderObject.getPositionForPoint(handleTargetGlobal); + + if (_selection.isCollapsed) { + _selectionOverlay.updateMagnifier(_buildMagnifier( + currentTextPosition: position, + globalGesturePosition: details.globalPosition, + renderEditable: renderObject, + )); + + final TextSelection currentSelection = + TextSelection.fromPosition(position); + _handleSelectionHandleChanged(currentSelection); + return; + } + + final TextSelection newSelection; + switch (defaultTargetPlatform) { + // On Apple platforms, dragging the base handle makes it the extent. + case TargetPlatform.iOS: + case TargetPlatform.macOS: + newSelection = TextSelection( + extentOffset: position.offset, + baseOffset: _selection.start, + ); + if (position.offset <= _selection.start) { + return; // Don't allow order swapping. + } + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + newSelection = TextSelection( + baseOffset: _selection.baseOffset, + extentOffset: position.offset, + ); + if (newSelection.baseOffset >= newSelection.extentOffset) { + return; // Don't allow order swapping. + } + } + + _handleSelectionHandleChanged(newSelection); + + _selectionOverlay.updateMagnifier(_buildMagnifier( + currentTextPosition: newSelection.extent, + globalGesturePosition: details.globalPosition, + renderEditable: renderObject, + )); + } + + // The contact position of the gesture at the current start handle location, + // in global coordinates. Updated when the handle moves. + late double _startHandleDragPosition; + + // The distance from _startHandleDragPosition to the center of the line that + // it corresponds to, in global coordinates. + late double _startHandleDragTarget; + + void _handleSelectionStartHandleDragStart(DragStartDetails details) { + if (!renderObject.attached) { + return; + } + + _startHandleDragPosition = details.globalPosition.dy; + + // Use local coordinates when dealing with line height. because in case of a + // scale transformation, the line height will also be scaled. + final double centerOfLineLocal = + _selectionOverlay.selectionEndpoints.first.point.dy - + renderObject.preferredLineHeight / 2; + final double centerOfLineGlobal = renderObject + .localToGlobal( + Offset(0.0, centerOfLineLocal), + ) + .dy; + _startHandleDragTarget = centerOfLineGlobal - details.globalPosition.dy; + // Instead of finding the TextPosition at the handle's location directly, + // use the vertical center of the line that it points to. This is because + // selection handles typically hang above or below the line that they point + // to. + final TextPosition position = renderObject.getPositionForPoint( + Offset( + details.globalPosition.dx, + centerOfLineGlobal, + ), + ); + + _selectionOverlay.showMagnifier( + _buildMagnifier( + currentTextPosition: position, + globalGesturePosition: details.globalPosition, + renderEditable: renderObject, + ), + ); + } + + void _handleSelectionStartHandleDragUpdate(DragUpdateDetails details) { + if (!renderObject.attached) { + return; + } + + // This is NOT the same as details.localPosition. That is relative to the + // selection handle, whereas this is relative to the RenderEditable. + final Offset localPosition = + renderObject.globalToLocal(details.globalPosition); + final double nextStartHandleDragPositionLocal = _getHandleDy( + localPosition.dy, + renderObject.globalToLocal(Offset(0.0, _startHandleDragPosition)).dy, + ); + _startHandleDragPosition = renderObject + .localToGlobal( + Offset(0.0, nextStartHandleDragPositionLocal), + ) + .dy; + final Offset handleTargetGlobal = Offset( + details.globalPosition.dx, + _startHandleDragPosition + _startHandleDragTarget, + ); + final TextPosition position = + renderObject.getPositionForPoint(handleTargetGlobal); + + if (_selection.isCollapsed) { + _selectionOverlay.updateMagnifier(_buildMagnifier( + currentTextPosition: position, + globalGesturePosition: details.globalPosition, + renderEditable: renderObject, + )); + + final TextSelection currentSelection = + TextSelection.fromPosition(position); + _handleSelectionHandleChanged(currentSelection); + return; + } + + final TextSelection newSelection; + switch (defaultTargetPlatform) { + // On Apple platforms, dragging the base handle makes it the extent. + case TargetPlatform.iOS: + case TargetPlatform.macOS: + newSelection = TextSelection( + extentOffset: position.offset, + baseOffset: _selection.end, + ); + if (newSelection.extentOffset >= _selection.end) { + return; // Don't allow order swapping. + } + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + newSelection = TextSelection( + baseOffset: position.offset, + extentOffset: _selection.extentOffset, + ); + if (newSelection.baseOffset >= newSelection.extentOffset) { + return; // Don't allow order swapping. + } + } + + _selectionOverlay.updateMagnifier(_buildMagnifier( + currentTextPosition: newSelection.extent.offset < newSelection.base.offset + ? newSelection.extent + : newSelection.base, + globalGesturePosition: details.globalPosition, + renderEditable: renderObject, + )); + + _handleSelectionHandleChanged(newSelection); + } + + void _handleAnyDragEnd(DragEndDetails details) { + if (!context.mounted) { + return; + } + if (selectionControls is! TextSelectionHandleControls) { + _selectionOverlay.hideMagnifier(); + if (!_selection.isCollapsed) { + _selectionOverlay.showToolbar(); + } + return; + } + _selectionOverlay.hideMagnifier(); + if (!_selection.isCollapsed) { + _selectionOverlay.showToolbar( + context: context, + contextMenuBuilder: contextMenuBuilder, + ); + } + } + + void _handleSelectionHandleChanged(TextSelection newSelection) { + selectionDelegate.userUpdateTextEditingValue( + _value.copyWith(selection: newSelection), + SelectionChangedCause.drag, + ); + } + + TextSelectionHandleType _chooseType( + TextDirection textDirection, + TextSelectionHandleType ltrType, + TextSelectionHandleType rtlType, + ) { + if (_selection.isCollapsed) { + return TextSelectionHandleType.collapsed; + } + + return switch (textDirection) { + TextDirection.ltr => ltrType, + TextDirection.rtl => rtlType, + }; + } +} + +/// An object that manages a pair of selection handles and a toolbar. +/// +/// The selection handles are displayed in the [Overlay] that most closely +/// encloses the given [BuildContext]. +class _SelectionOverlay { + /// Creates an object that manages overlay entries for selection handles. + /// + /// The [context] must have an [Overlay] as an ancestor. + _SelectionOverlay({ + required this.context, + this.debugRequiredFor, + required TextSelectionHandleType startHandleType, + required double lineHeightAtStart, + this.startHandlesVisible, + this.onStartHandleDragStart, + this.onStartHandleDragUpdate, + this.onStartHandleDragEnd, + required TextSelectionHandleType endHandleType, + required double lineHeightAtEnd, + this.endHandlesVisible, + this.onEndHandleDragStart, + this.onEndHandleDragUpdate, + this.onEndHandleDragEnd, + this.toolbarVisible, + required List selectionEndpoints, + required this.selectionControls, + @Deprecated( + 'Use `contextMenuBuilder` in `showToolbar` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + required this.selectionDelegate, + required this.clipboardStatus, + required this.startHandleLayerLink, + required this.endHandleLayerLink, + required this.toolbarLayerLink, + this.dragStartBehavior = DragStartBehavior.start, + this.onSelectionHandleTapped, + @Deprecated( + 'Use `contextMenuBuilder` in `showToolbar` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + Offset? toolbarLocation, + this.magnifierConfiguration = TextMagnifierConfiguration.disabled, + }) : _startHandleType = startHandleType, + _lineHeightAtStart = lineHeightAtStart, + _endHandleType = endHandleType, + _lineHeightAtEnd = lineHeightAtEnd, + _selectionEndpoints = selectionEndpoints, + _toolbarLocation = toolbarLocation, + assert(debugCheckHasOverlay(context)) { + // TODO(polina-c): stop duplicating code across disposables + // https://github.com/flutter/flutter/issues/137435 + if (kFlutterMemoryAllocationsEnabled) { + FlutterMemoryAllocations.instance.dispatchObjectCreated( + library: 'package:flutter/widgets.dart', + className: '$SelectionOverlay', + object: this, + ); + } + } + + /// {@macro flutter.widgets.SelectionOverlay.context} + final BuildContext context; + + final ValueNotifier _magnifierInfo = + ValueNotifier(MagnifierInfo.empty); + + // [MagnifierController.show] and [MagnifierController.hide] should not be + // called directly, except from inside [showMagnifier] and [hideMagnifier]. If + // it is desired to show or hide the magnifier, call [showMagnifier] or + // [hideMagnifier]. This is because the magnifier needs to orchestrate with + // other properties in [SelectionOverlay]. + final MagnifierController _magnifierController = MagnifierController(); + + /// The configuration for the magnifier. + /// + /// By default, [SelectionOverlay]'s [TextMagnifierConfiguration] is disabled. + /// + /// {@macro flutter.widgets.magnifier.intro} + final TextMagnifierConfiguration magnifierConfiguration; + + /// {@template flutter.widgets.SelectionOverlay.toolbarIsVisible} + /// Whether the toolbar is currently visible. + /// + /// Includes both the text selection toolbar and the spell check menu. + /// {@endtemplate} + bool get toolbarIsVisible { + return selectionControls is TextSelectionHandleControls + ? _contextMenuController.isShown || _spellCheckToolbarController.isShown + : _toolbar != null || _spellCheckToolbarController.isShown; + } + + /// {@template flutter.widgets.SelectionOverlay.showMagnifier} + /// Shows the magnifier, and hides the toolbar if it was showing when [showMagnifier] + /// was called. This is safe to call on platforms not mobile, since + /// a magnifierBuilder will not be provided, or the magnifierBuilder will return null + /// on platforms not mobile. + /// + /// This is NOT the source of truth for if the magnifier is up or not, + /// since magnifiers may hide themselves. If this info is needed, check + /// [MagnifierController.shown]. + /// {@endtemplate} + void showMagnifier(MagnifierInfo initialMagnifierInfo) { + if (toolbarIsVisible) { + hideToolbar(); + } + + // Start from empty, so we don't utilize any remnant values. + _magnifierInfo.value = initialMagnifierInfo; + + // Pre-build the magnifiers so we can tell if we've built something + // or not. If we don't build a magnifiers, then we should not + // insert anything in the overlay. + final Widget? builtMagnifier = magnifierConfiguration.magnifierBuilder( + context, + _magnifierController, + _magnifierInfo, + ); + + if (builtMagnifier == null) { + return; + } + + _magnifierController.show( + context: context, + below: magnifierConfiguration.shouldDisplayHandlesInMagnifier + ? null + : _handles?.start, + builder: (_) => builtMagnifier, + ); + } + + /// {@template flutter.widgets.SelectionOverlay.hideMagnifier} + /// Hide the current magnifier. + /// + /// This does nothing if there is no magnifier. + /// {@endtemplate} + void hideMagnifier() { + // This cannot be a check on `MagnifierController.shown`, since + // it's possible that the magnifier is still in the overlay, but + // not shown in cases where the magnifier hides itself. + if (_magnifierController.overlayEntry == null) { + return; + } + + _magnifierController.hide(); + } + + /// The type of start selection handle. + /// + /// Changing the value while the handles are visible causes them to rebuild. + TextSelectionHandleType get startHandleType => _startHandleType; + TextSelectionHandleType _startHandleType; + set startHandleType(TextSelectionHandleType value) { + if (_startHandleType == value) { + return; + } + _startHandleType = value; + markNeedsBuild(); + } + + /// The line height at the selection start. + /// + /// This value is used for calculating the size of the start selection handle. + /// + /// Changing the value while the handles are visible causes them to rebuild. + double get lineHeightAtStart => _lineHeightAtStart; + double _lineHeightAtStart; + set lineHeightAtStart(double value) { + if (_lineHeightAtStart == value) { + return; + } + _lineHeightAtStart = value; + markNeedsBuild(); + } + + bool _isDraggingStartHandle = false; + + /// Whether the start handle is visible. + /// + /// If the value changes, the start handle uses [FadeTransition] to transition + /// itself on and off the screen. + /// + /// If this is null, the start selection handle will always be visible. + final ValueListenable? startHandlesVisible; + + /// Called when the users start dragging the start selection handles. + final ValueChanged? onStartHandleDragStart; + + void _handleStartHandleDragStart(DragStartDetails details) { + assert(!_isDraggingStartHandle); + // Calling OverlayEntry.remove may not happen until the following frame, so + // it's possible for the handles to receive a gesture after calling remove. + if (_handles == null) { + _isDraggingStartHandle = false; + return; + } + _isDraggingStartHandle = details.kind == PointerDeviceKind.touch; + onStartHandleDragStart?.call(details); + } + + void _handleStartHandleDragUpdate(DragUpdateDetails details) { + // Calling OverlayEntry.remove may not happen until the following frame, so + // it's possible for the handles to receive a gesture after calling remove. + if (_handles == null) { + _isDraggingStartHandle = false; + return; + } + onStartHandleDragUpdate?.call(details); + } + + /// Called when the users drag the start selection handles to new locations. + final ValueChanged? onStartHandleDragUpdate; + + /// Called when the users lift their fingers after dragging the start selection + /// handles. + final ValueChanged? onStartHandleDragEnd; + + void _handleStartHandleDragEnd(DragEndDetails details) { + _isDraggingStartHandle = false; + // Calling OverlayEntry.remove may not happen until the following frame, so + // it's possible for the handles to receive a gesture after calling remove. + if (_handles == null) { + return; + } + onStartHandleDragEnd?.call(details); + } + + /// The type of end selection handle. + /// + /// Changing the value while the handles are visible causes them to rebuild. + TextSelectionHandleType get endHandleType => _endHandleType; + TextSelectionHandleType _endHandleType; + set endHandleType(TextSelectionHandleType value) { + if (_endHandleType == value) { + return; + } + _endHandleType = value; + markNeedsBuild(); + } + + /// The line height at the selection end. + /// + /// This value is used for calculating the size of the end selection handle. + /// + /// Changing the value while the handles are visible causes them to rebuild. + double get lineHeightAtEnd => _lineHeightAtEnd; + double _lineHeightAtEnd; + set lineHeightAtEnd(double value) { + if (_lineHeightAtEnd == value) { + return; + } + _lineHeightAtEnd = value; + markNeedsBuild(); + } + + bool _isDraggingEndHandle = false; + + /// Whether the end handle is visible. + /// + /// If the value changes, the end handle uses [FadeTransition] to transition + /// itself on and off the screen. + /// + /// If this is null, the end selection handle will always be visible. + final ValueListenable? endHandlesVisible; + + /// Called when the users start dragging the end selection handles. + final ValueChanged? onEndHandleDragStart; + + void _handleEndHandleDragStart(DragStartDetails details) { + assert(!_isDraggingEndHandle); + // Calling OverlayEntry.remove may not happen until the following frame, so + // it's possible for the handles to receive a gesture after calling remove. + if (_handles == null) { + _isDraggingEndHandle = false; + return; + } + _isDraggingEndHandle = details.kind == PointerDeviceKind.touch; + onEndHandleDragStart?.call(details); + } + + void _handleEndHandleDragUpdate(DragUpdateDetails details) { + // Calling OverlayEntry.remove may not happen until the following frame, so + // it's possible for the handles to receive a gesture after calling remove. + if (_handles == null) { + _isDraggingEndHandle = false; + return; + } + onEndHandleDragUpdate?.call(details); + } + + /// Called when the users drag the end selection handles to new locations. + final ValueChanged? onEndHandleDragUpdate; + + /// Called when the users lift their fingers after dragging the end selection + /// handles. + final ValueChanged? onEndHandleDragEnd; + + void _handleEndHandleDragEnd(DragEndDetails details) { + _isDraggingEndHandle = false; + // Calling OverlayEntry.remove may not happen until the following frame, so + // it's possible for the handles to receive a gesture after calling remove. + if (_handles == null) { + return; + } + onEndHandleDragEnd?.call(details); + } + + /// Whether the toolbar is visible. + /// + /// If the value changes, the toolbar uses [FadeTransition] to transition + /// itself on and off the screen. + /// + /// If this is null the toolbar will always be visible. + final ValueListenable? toolbarVisible; + + /// The text selection positions of selection start and end. + List get selectionEndpoints => _selectionEndpoints; + List _selectionEndpoints; + set selectionEndpoints(List value) { + if (!listEquals(_selectionEndpoints, value)) { + markNeedsBuild(); + if (_isDraggingEndHandle || _isDraggingStartHandle) { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + HapticFeedback.selectionClick(); + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + break; + } + } + } + _selectionEndpoints = value; + } + + /// Debugging information for explaining why the [Overlay] is required. + final Widget? debugRequiredFor; + + /// The object supplied to the [CompositedTransformTarget] that wraps the text + /// field. + final LayerLink toolbarLayerLink; + + /// The objects supplied to the [CompositedTransformTarget] that wraps the + /// location of start selection handle. + final LayerLink startHandleLayerLink; + + /// The objects supplied to the [CompositedTransformTarget] that wraps the + /// location of end selection handle. + final LayerLink endHandleLayerLink; + + /// {@template flutter.widgets.SelectionOverlay.selectionControls} + /// Builds text selection handles and toolbar. + /// {@endtemplate} + final TextSelectionControls? selectionControls; + + /// {@template flutter.widgets.SelectionOverlay.selectionDelegate} + /// The delegate for manipulating the current selection in the owning + /// text field. + /// {@endtemplate} + @Deprecated( + 'Use `contextMenuBuilder` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + final TextSelectionDelegate? selectionDelegate; + + /// Determines the way that drag start behavior is handled. + /// + /// If set to [DragStartBehavior.start], handle drag behavior will + /// begin at the position where the drag gesture won the arena. If set to + /// [DragStartBehavior.down] it will begin at the position where a down + /// event is first detected. + /// + /// In general, setting this to [DragStartBehavior.start] will make drag + /// animation smoother and setting it to [DragStartBehavior.down] will make + /// drag behavior feel slightly more reactive. + /// + /// By default, the drag start behavior is [DragStartBehavior.start]. + /// + /// See also: + /// + /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. + final DragStartBehavior dragStartBehavior; + + /// {@template flutter.widgets.SelectionOverlay.onSelectionHandleTapped} + /// A callback that's optionally invoked when a selection handle is tapped. + /// + /// The [TextSelectionControls.buildHandle] implementation the text field + /// uses decides where the handle's tap "hotspot" is, or whether the + /// selection handle supports tap gestures at all. For instance, + /// [MaterialTextSelectionControls] calls [onSelectionHandleTapped] when the + /// selection handle's "knob" is tapped, while + /// [CupertinoTextSelectionControls] builds a handle that's not sufficiently + /// large for tapping (as it's not meant to be tapped) so it does not call + /// [onSelectionHandleTapped] even when tapped. + /// {@endtemplate} + // See https://github.com/flutter/flutter/issues/39376#issuecomment-848406415 + // for provenance. + final VoidCallback? onSelectionHandleTapped; + + /// Maintains the status of the clipboard for determining if its contents can + /// be pasted or not. + /// + /// Useful because the actual value of the clipboard can only be checked + /// asynchronously (see [Clipboard.getData]). + final ClipboardStatusNotifier? clipboardStatus; + + /// The location of where the toolbar should be drawn in relative to the + /// location of [toolbarLayerLink]. + /// + /// If this is null, the toolbar is drawn based on [selectionEndpoints] and + /// the rect of render object of [context]. + /// + /// This is useful for displaying toolbars at the mouse right-click locations + /// in desktop devices. + @Deprecated( + 'Use the `contextMenuBuilder` parameter in `showToolbar` instead. ' + 'This feature was deprecated after v3.3.0-0.5.pre.', + ) + Offset? get toolbarLocation => _toolbarLocation; + Offset? _toolbarLocation; + set toolbarLocation(Offset? value) { + if (_toolbarLocation == value) { + return; + } + _toolbarLocation = value; + markNeedsBuild(); + } + + /// Controls the fade-in and fade-out animations for the toolbar and handles. + // static const Duration fadeDuration = Duration(milliseconds: 150); + + /// A pair of handles. If this is non-null, there are always 2, though the + /// second is hidden when the selection is collapsed. + ({OverlayEntry start, OverlayEntry end})? _handles; + + /// A copy/paste toolbar. + OverlayEntry? _toolbar; + + // Manages the context menu. Not necessarily visible when non-null. + final ContextMenuController _contextMenuController = ContextMenuController(); + + final ContextMenuController _spellCheckToolbarController = + ContextMenuController(); + + /// {@template flutter.widgets.SelectionOverlay.showHandles} + /// Builds the handles by inserting them into the [context]'s overlay. + /// {@endtemplate} + void showHandles() { + if (_handles != null) { + return; + } + + final OverlayState overlay = Overlay.of(context, + rootOverlay: true, debugRequiredFor: debugRequiredFor); + + final CapturedThemes capturedThemes = InheritedTheme.capture( + from: context, + to: overlay.context, + ); + + _handles = ( + start: OverlayEntry(builder: (BuildContext context) { + return capturedThemes.wrap(_buildStartHandle(context)); + }), + end: OverlayEntry(builder: (BuildContext context) { + return capturedThemes.wrap(_buildEndHandle(context)); + }), + ); + overlay.insertAll([_handles!.start, _handles!.end]); + } + + /// {@template flutter.widgets.SelectionOverlay.hideHandles} + /// Destroys the handles by removing them from overlay. + /// {@endtemplate} + void hideHandles() { + if (_handles != null) { + _handles!.start.remove(); + _handles!.start.dispose(); + _handles!.end.remove(); + _handles!.end.dispose(); + _handles = null; + } + } + + /// {@template flutter.widgets.SelectionOverlay.showToolbar} + /// Shows the toolbar by inserting it into the [context]'s overlay. + /// {@endtemplate} + void showToolbar({ + BuildContext? context, + WidgetBuilder? contextMenuBuilder, + }) { + if (contextMenuBuilder == null) { + if (_toolbar != null) { + return; + } + _toolbar = OverlayEntry(builder: _buildToolbar); + Overlay.of(this.context, + rootOverlay: true, debugRequiredFor: debugRequiredFor) + .insert(_toolbar!); + return; + } + + if (context == null) { + return; + } + + final RenderBox renderBox = context.findRenderObject()! as RenderBox; + _contextMenuController.show( + context: context, + contextMenuBuilder: (BuildContext context) { + return _SelectionToolbarWrapper( + visibility: toolbarVisible, + layerLink: toolbarLayerLink, + offset: -renderBox.localToGlobal(Offset.zero), + child: contextMenuBuilder(context), + ); + }, + ); + } + + /// Shows toolbar with spell check suggestions of misspelled words that are + /// available for click-and-replace. + void showSpellCheckSuggestionsToolbar({ + BuildContext? context, + required WidgetBuilder builder, + }) { + if (context == null) { + return; + } + + final RenderBox renderBox = context.findRenderObject()! as RenderBox; + _spellCheckToolbarController.show( + context: context, + contextMenuBuilder: (BuildContext context) { + return _SelectionToolbarWrapper( + layerLink: toolbarLayerLink, + offset: -renderBox.localToGlobal(Offset.zero), + child: builder(context), + ); + }, + ); + } + + bool _buildScheduled = false; + + /// Rebuilds the selection toolbar or handles if they are present. + void markNeedsBuild() { + if (_handles == null && _toolbar == null) { + return; + } + // If we are in build state, it will be too late to update visibility. + // We will need to schedule the build in next frame. + if (SchedulerBinding.instance.schedulerPhase == + SchedulerPhase.persistentCallbacks) { + if (_buildScheduled) { + return; + } + _buildScheduled = true; + SchedulerBinding.instance.addPostFrameCallback((Duration duration) { + _buildScheduled = false; + if (_handles != null) { + _handles!.start.markNeedsBuild(); + _handles!.end.markNeedsBuild(); + } + _toolbar?.markNeedsBuild(); + if (_contextMenuController.isShown) { + _contextMenuController.markNeedsBuild(); + } else if (_spellCheckToolbarController.isShown) { + _spellCheckToolbarController.markNeedsBuild(); + } + }, debugLabel: 'SelectionOverlay.markNeedsBuild'); + } else { + if (_handles != null) { + _handles!.start.markNeedsBuild(); + _handles!.end.markNeedsBuild(); + } + _toolbar?.markNeedsBuild(); + if (_contextMenuController.isShown) { + _contextMenuController.markNeedsBuild(); + } else if (_spellCheckToolbarController.isShown) { + _spellCheckToolbarController.markNeedsBuild(); + } + } + } + + /// {@template flutter.widgets.SelectionOverlay.hide} + /// Hides the entire overlay including the toolbar and the handles. + /// {@endtemplate} + void hide() { + _magnifierController.hide(); + hideHandles(); + if (_toolbar != null || + _contextMenuController.isShown || + _spellCheckToolbarController.isShown) { + hideToolbar(); + } + } + + /// {@template flutter.widgets.SelectionOverlay.hideToolbar} + /// Hides the toolbar part of the overlay. + /// + /// To hide the whole overlay, see [hide]. + /// {@endtemplate} + void hideToolbar() { + _contextMenuController.remove(); + _spellCheckToolbarController.remove(); + if (_toolbar == null) { + return; + } + _toolbar?.remove(); + _toolbar?.dispose(); + _toolbar = null; + } + + /// {@template flutter.widgets.SelectionOverlay.dispose} + /// Disposes this object and release resources. + /// {@endtemplate} + void dispose() { + // TODO(polina-c): stop duplicating code across disposables + // https://github.com/flutter/flutter/issues/137435 + if (kFlutterMemoryAllocationsEnabled) { + FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this); + } + hide(); + _magnifierInfo.dispose(); + } + + Widget _buildStartHandle(BuildContext context) { + final Widget handle; + final TextSelectionControls? selectionControls = this.selectionControls; + if (selectionControls == null) { + handle = const SizedBox.shrink(); + } else { + handle = _SelectionHandleOverlay( + type: _startHandleType, + handleLayerLink: startHandleLayerLink, + onSelectionHandleTapped: onSelectionHandleTapped, + onSelectionHandleDragStart: _handleStartHandleDragStart, + onSelectionHandleDragUpdate: _handleStartHandleDragUpdate, + onSelectionHandleDragEnd: _handleStartHandleDragEnd, + selectionControls: selectionControls, + visibility: startHandlesVisible, + preferredLineHeight: _lineHeightAtStart, + dragStartBehavior: dragStartBehavior, + ); + } + return TextFieldTapRegion( + child: ExcludeSemantics( + child: handle, + ), + ); + } + + Widget _buildEndHandle(BuildContext context) { + final Widget handle; + final TextSelectionControls? selectionControls = this.selectionControls; + if (selectionControls == null || + _startHandleType == TextSelectionHandleType.collapsed) { + // Hide the second handle when collapsed. + handle = const SizedBox.shrink(); + } else { + handle = _SelectionHandleOverlay( + type: _endHandleType, + handleLayerLink: endHandleLayerLink, + onSelectionHandleTapped: onSelectionHandleTapped, + onSelectionHandleDragStart: _handleEndHandleDragStart, + onSelectionHandleDragUpdate: _handleEndHandleDragUpdate, + onSelectionHandleDragEnd: _handleEndHandleDragEnd, + selectionControls: selectionControls, + visibility: endHandlesVisible, + preferredLineHeight: _lineHeightAtEnd, + dragStartBehavior: dragStartBehavior, + ); + } + return TextFieldTapRegion( + child: ExcludeSemantics( + child: handle, + ), + ); + } + + // Build the toolbar via TextSelectionControls. + Widget _buildToolbar(BuildContext context) { + if (selectionControls == null) { + return const SizedBox.shrink(); + } + assert(selectionDelegate != null, + 'If not using contextMenuBuilder, must pass selectionDelegate.'); + + final RenderBox renderBox = this.context.findRenderObject()! as RenderBox; + + final Rect editingRegion = Rect.fromPoints( + renderBox.localToGlobal(Offset.zero), + renderBox.localToGlobal(renderBox.size.bottomRight(Offset.zero)), + ); + + final bool isMultiline = + selectionEndpoints.last.point.dy - selectionEndpoints.first.point.dy > + lineHeightAtEnd / 2; + + // If the selected text spans more than 1 line, horizontally center the toolbar. + // Derived from both iOS and Android. + final double midX = isMultiline + ? editingRegion.width / 2 + : (selectionEndpoints.first.point.dx + + selectionEndpoints.last.point.dx) / + 2; + + final Offset midpoint = Offset( + midX, + // The y-coordinate won't be made use of most likely. + selectionEndpoints.first.point.dy - lineHeightAtStart, + ); + + return _SelectionToolbarWrapper( + visibility: toolbarVisible, + layerLink: toolbarLayerLink, + offset: -editingRegion.topLeft, + child: Builder( + builder: (BuildContext context) { + return selectionControls!.buildToolbar( + context, + editingRegion, + lineHeightAtStart, + midpoint, + selectionEndpoints, + selectionDelegate!, + clipboardStatus, + toolbarLocation, + ); + }, + ), + ); + } + + /// {@template flutter.widgets.SelectionOverlay.updateMagnifier} + /// Update the current magnifier with new selection data, so the magnifier + /// can respond accordingly. + /// + /// If the magnifier is not shown, this still updates the magnifier position + /// because the magnifier may have hidden itself and is looking for a cue to reshow + /// itself. + /// + /// If there is no magnifier in the overlay, this does nothing. + /// {@endtemplate} + void updateMagnifier(MagnifierInfo magnifierInfo) { + if (_magnifierController.overlayEntry == null) { + return; + } + + _magnifierInfo.value = magnifierInfo; + } +} + +// TODO(justinmc): Currently this fades in but not out on all platforms. It +// should follow the correct fading behavior for the current platform, then be +// made public and de-duplicated with widgets/selectable_region.dart. +// https://github.com/flutter/flutter/issues/107732 +// Wrap the given child in the widgets common to both contextMenuBuilder and +// TextSelectionControls.buildToolbar. +class _SelectionToolbarWrapper extends StatefulWidget { + const _SelectionToolbarWrapper({ + this.visibility, + required this.layerLink, + required this.offset, + required this.child, + }); + + final Widget child; + final Offset offset; + final LayerLink layerLink; + final ValueListenable? visibility; + + @override + State<_SelectionToolbarWrapper> createState() => + _SelectionToolbarWrapperState(); +} + +class _SelectionToolbarWrapperState extends State<_SelectionToolbarWrapper> + with SingleTickerProviderStateMixin { + late AnimationController _controller; + Animation get _opacity => _controller.view; + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + duration: SelectionOverlay.fadeDuration, vsync: this); + + _toolbarVisibilityChanged(); + widget.visibility?.addListener(_toolbarVisibilityChanged); + } + + @override + void didUpdateWidget(_SelectionToolbarWrapper oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.visibility == widget.visibility) { + return; + } + oldWidget.visibility?.removeListener(_toolbarVisibilityChanged); + _toolbarVisibilityChanged(); + widget.visibility?.addListener(_toolbarVisibilityChanged); + } + + @override + void dispose() { + widget.visibility?.removeListener(_toolbarVisibilityChanged); + _controller.dispose(); + super.dispose(); + } + + void _toolbarVisibilityChanged() { + if (widget.visibility?.value ?? true) { + _controller.forward(); + } else { + _controller.reverse(); + } + } + + @override + Widget build(BuildContext context) { + return TextFieldTapRegion( + child: Directionality( + textDirection: Directionality.of(this.context), + child: FadeTransition( + opacity: _opacity, + child: CompositedTransformFollower( + link: widget.layerLink, + showWhenUnlinked: false, + offset: widget.offset, + child: widget.child, + ), + ), + ), + ); + } +} + +/// This widget represents a single draggable selection handle. +class _SelectionHandleOverlay extends StatefulWidget { + /// Create selection overlay. + const _SelectionHandleOverlay({ + required this.type, + required this.handleLayerLink, + this.onSelectionHandleTapped, + this.onSelectionHandleDragStart, + this.onSelectionHandleDragUpdate, + this.onSelectionHandleDragEnd, + required this.selectionControls, + this.visibility, + required this.preferredLineHeight, + this.dragStartBehavior = DragStartBehavior.start, + }); + + final LayerLink handleLayerLink; + final VoidCallback? onSelectionHandleTapped; + final ValueChanged? onSelectionHandleDragStart; + final ValueChanged? onSelectionHandleDragUpdate; + final ValueChanged? onSelectionHandleDragEnd; + final TextSelectionControls selectionControls; + final ValueListenable? visibility; + final double preferredLineHeight; + final TextSelectionHandleType type; + final DragStartBehavior dragStartBehavior; + + @override + State<_SelectionHandleOverlay> createState() => + _SelectionHandleOverlayState(); +} + +class _SelectionHandleOverlayState extends State<_SelectionHandleOverlay> + with SingleTickerProviderStateMixin { + late AnimationController _controller; + Animation get _opacity => _controller.view; + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + duration: SelectionOverlay.fadeDuration, vsync: this); + + _handleVisibilityChanged(); + widget.visibility?.addListener(_handleVisibilityChanged); + } + + void _handleVisibilityChanged() { + if (widget.visibility?.value ?? true) { + _controller.forward(); + } else { + _controller.reverse(); + } + } + + @override + void didUpdateWidget(_SelectionHandleOverlay oldWidget) { + super.didUpdateWidget(oldWidget); + oldWidget.visibility?.removeListener(_handleVisibilityChanged); + _handleVisibilityChanged(); + widget.visibility?.addListener(_handleVisibilityChanged); + } + + @override + void dispose() { + widget.visibility?.removeListener(_handleVisibilityChanged); + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final Offset handleAnchor = widget.selectionControls.getHandleAnchor( + widget.type, + widget.preferredLineHeight, + ); + final Size handleSize = widget.selectionControls.getHandleSize( + widget.preferredLineHeight, + ); + + final Rect handleRect = Rect.fromLTWH( + -handleAnchor.dx, + -handleAnchor.dy, + handleSize.width, + handleSize.height, + ); + + // Make sure the GestureDetector is big enough to be easily interactive. + final Rect interactiveRect = handleRect.expandToInclude( + Rect.fromCircle( + center: handleRect.center, radius: kMinInteractiveDimension / 2), + ); + final RelativeRect padding = RelativeRect.fromLTRB( + math.max((interactiveRect.width - handleRect.width) / 2, 0), + math.max((interactiveRect.height - handleRect.height) / 2, 0), + math.max((interactiveRect.width - handleRect.width) / 2, 0), + math.max((interactiveRect.height - handleRect.height) / 2, 0), + ); + + // Make sure a drag is eagerly accepted. This is used on iOS to match the + // behavior where a drag directly on a collapse handle will always win against + // other drag gestures. + final bool eagerlyAcceptDragWhenCollapsed = + widget.type == TextSelectionHandleType.collapsed && + defaultTargetPlatform == TargetPlatform.iOS; + + return CompositedTransformFollower( + link: widget.handleLayerLink, + offset: interactiveRect.topLeft, + showWhenUnlinked: false, + child: FadeTransition( + opacity: _opacity, + child: Container( + alignment: Alignment.topLeft, + width: interactiveRect.width, + height: interactiveRect.height, + child: RawGestureDetector( + behavior: HitTestBehavior.translucent, + gestures: { + PanGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => PanGestureRecognizer( + debugOwner: this, + // Mouse events select the text and do not drag the cursor. + supportedDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.stylus, + PointerDeviceKind.unknown, + }, + ), + (PanGestureRecognizer instance) { + instance + ..dragStartBehavior = widget.dragStartBehavior + ..gestureSettings = eagerlyAcceptDragWhenCollapsed + ? const DeviceGestureSettings(touchSlop: 1.0) + : null + ..onStart = widget.onSelectionHandleDragStart + ..onUpdate = widget.onSelectionHandleDragUpdate + ..onEnd = widget.onSelectionHandleDragEnd; + }, + ), + }, + child: Padding( + padding: EdgeInsets.only( + left: padding.left, + top: padding.top, + right: padding.right, + bottom: padding.bottom, + ), + child: widget.selectionControls.buildHandle( + context, + widget.type, + widget.preferredLineHeight, + widget.onSelectionHandleTapped, + ), + ), + ), + ), + ), + ); + } +} + +/// Delegate interface for the [TextSelectionGestureDetectorBuilder]. +/// +/// The interface is usually implemented by the [State] of text field +/// implementations wrapping [EditableText], so that they can use a +/// [TextSelectionGestureDetectorBuilder] to build a +/// [TextSelectionGestureDetector] for their [EditableText]. The delegate +/// provides the builder with information about the current state of the text +/// field. Based on that information, the builder adds the correct gesture +/// handlers to the gesture detector. +/// +/// See also: +/// +/// * [TextField], which implements this delegate for the Material text field. +/// * [CupertinoTextField], which implements this delegate for the Cupertino +/// text field. +abstract class _TextSelectionGestureDetectorBuilderDelegate { + /// [GlobalKey] to the [EditableText] for which the + /// [TextSelectionGestureDetectorBuilder] will build a [TextSelectionGestureDetector]. + GlobalKey<_EditableTextState> get editableTextKey; + + /// Whether the text field should respond to force presses. + bool get forcePressEnabled; + + /// Whether the user may select text in the text field. + bool get selectionEnabled; +} + +/// Builds a [TextSelectionGestureDetector] to wrap an [EditableText]. +/// +/// The class implements sensible defaults for many user interactions +/// with an [EditableText] (see the documentation of the various gesture handler +/// methods, e.g. [onTapDown], [onForcePressStart], etc.). Subclasses of +/// [TextSelectionGestureDetectorBuilder] can change the behavior performed in +/// responds to these gesture events by overriding the corresponding handler +/// methods of this class. +/// +/// The resulting [TextSelectionGestureDetector] to wrap an [EditableText] is +/// obtained by calling [buildGestureDetector]. +/// +/// A [TextSelectionGestureDetectorBuilder] must be provided a +/// [TextSelectionGestureDetectorBuilderDelegate], from which information about +/// the [EditableText] may be obtained. Typically, the [State] of the widget +/// that builds the [EditableText] implements this interface, and then passes +/// itself as the [delegate]. +/// +/// See also: +/// +/// * [TextField], which uses a subclass to implement the Material-specific +/// gesture logic of an [EditableText]. +/// * [CupertinoTextField], which uses a subclass to implement the +/// Cupertino-specific gesture logic of an [EditableText]. +class _TextSelectionGestureDetectorBuilder { + /// Creates a [TextSelectionGestureDetectorBuilder]. + _TextSelectionGestureDetectorBuilder({ + required this.delegate, + }); + + /// The delegate for this [TextSelectionGestureDetectorBuilder]. + /// + /// The delegate provides the builder with information about what actions can + /// currently be performed on the text field. Based on this, the builder adds + /// the correct gesture handlers to the gesture detector. + /// + /// Typically implemented by a [State] of a widget that builds an + /// [EditableText]. + @protected + final _TextSelectionGestureDetectorBuilderDelegate delegate; + + // Shows the magnifier on supported platforms at the given offset, currently + // only Android and iOS. + void _showMagnifierIfSupportedByPlatform(Offset positionToShow) { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.iOS: + editableText.showMagnifier(positionToShow); + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + } + } + + // Hides the magnifier on supported platforms, currently only Android and iOS. + void _hideMagnifierIfSupportedByPlatform() { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.iOS: + editableText.hideMagnifier(); + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + } + } + + /// Returns true if lastSecondaryTapDownPosition was on selection. + bool get _lastSecondaryTapWasOnSelection { + assert(renderEditable.lastSecondaryTapDownPosition != null); + if (renderEditable.selection == null) { + return false; + } + + final TextPosition textPosition = renderEditable.getPositionForPoint( + renderEditable.lastSecondaryTapDownPosition!, + ); + + return renderEditable.selection!.start <= textPosition.offset && + renderEditable.selection!.end >= textPosition.offset; + } + + bool _positionWasOnSelectionExclusive(TextPosition textPosition) { + final TextSelection? selection = renderEditable.selection; + if (selection == null) { + return false; + } + + return selection.start < textPosition.offset && + selection.end > textPosition.offset; + } + + bool _positionWasOnSelectionInclusive(TextPosition textPosition) { + final TextSelection? selection = renderEditable.selection; + if (selection == null) { + return false; + } + + return selection.start <= textPosition.offset && + selection.end >= textPosition.offset; + } + + // Expand the selection to the given global position. + // + // Either base or extent will be moved to the last tapped position, whichever + // is closest. The selection will never shrink or pivot, only grow. + // + // If fromSelection is given, will expand from that selection instead of the + // current selection in renderEditable. + // + // See also: + // + // * [_extendSelection], which is similar but pivots the selection around + // the base. + void _expandSelection(Offset offset, SelectionChangedCause cause, + [TextSelection? fromSelection]) { + assert(renderEditable.selection?.baseOffset != null); + + final TextPosition tappedPosition = + renderEditable.getPositionForPoint(offset); + final TextSelection selection = fromSelection ?? renderEditable.selection!; + final bool baseIsCloser = + (tappedPosition.offset - selection.baseOffset).abs() < + (tappedPosition.offset - selection.extentOffset).abs(); + final TextSelection nextSelection = selection.copyWith( + baseOffset: baseIsCloser ? selection.extentOffset : selection.baseOffset, + extentOffset: tappedPosition.offset, + ); + + editableText.userUpdateTextEditingValue( + editableText.textEditingValue.copyWith( + selection: nextSelection, + ), + cause, + ); + } + + // Extend the selection to the given global position. + // + // Holds the base in place and moves the extent. + // + // See also: + // + // * [_expandSelection], which is similar but always increases the size of + // the selection. + void _extendSelection(Offset offset, SelectionChangedCause cause) { + assert(renderEditable.selection?.baseOffset != null); + + final TextPosition tappedPosition = + renderEditable.getPositionForPoint(offset); + final TextSelection selection = renderEditable.selection!; + final TextSelection nextSelection = selection.copyWith( + extentOffset: tappedPosition.offset, + ); + + editableText.userUpdateTextEditingValue( + editableText.textEditingValue.copyWith( + selection: nextSelection, + ), + cause, + ); + } + + /// Whether to show the selection toolbar. + /// + /// It is based on the signal source when a [onTapDown] is called. This getter + /// will return true if current [onTapDown] event is triggered by a touch or + /// a stylus. + bool get shouldShowSelectionToolbar => _shouldShowSelectionToolbar; + bool _shouldShowSelectionToolbar = true; + + /// The [State] of the [EditableText] for which the builder will provide a + /// [TextSelectionGestureDetector]. + @protected + + /// zmtzawqlp + _EditableTextState get editableText => delegate.editableTextKey.currentState!; + + /// The [RenderObject] of the [EditableText] for which the builder will + /// provide a [TextSelectionGestureDetector]. + @protected + // zmtzawqlp + _RenderEditable get renderEditable => editableText.renderEditable; + + /// Whether the Shift key was pressed when the most recent [PointerDownEvent] + /// was tracked by the [BaseTapAndDragGestureRecognizer]. + bool _isShiftPressed = false; + + /// The viewport offset pixels of any [Scrollable] containing the + /// [RenderEditable] at the last drag start. + double _dragStartScrollOffset = 0.0; + + /// The viewport offset pixels of the [RenderEditable] at the last drag start. + double _dragStartViewportOffset = 0.0; + + double get _scrollPosition { + final ScrollableState? scrollableState = + delegate.editableTextKey.currentContext == null + ? null + : Scrollable.maybeOf(delegate.editableTextKey.currentContext!); + return scrollableState == null ? 0.0 : scrollableState.position.pixels; + } + + AxisDirection? get _scrollDirection { + final ScrollableState? scrollableState = + delegate.editableTextKey.currentContext == null + ? null + : Scrollable.maybeOf(delegate.editableTextKey.currentContext!); + return scrollableState?.axisDirection; + } + + // For a shift + tap + drag gesture, the TextSelection at the point of the + // tap. Mac uses this value to reset to the original selection when an + // inversion of the base and offset happens. + TextSelection? _dragStartSelection; + + // For iOS long press behavior when the field is not focused. iOS uses this value + // to determine if a long press began on a field that was not focused. + // + // If the field was not focused when the long press began, a long press will select + // the word and a long press move will select word-by-word. If the field was + // focused, the cursor moves to the long press position. + bool _longPressStartedWithoutFocus = false; + + /// Handler for [TextSelectionGestureDetector.onTapTrackStart]. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onTapTrackStart], which triggers this + /// callback. + @protected + void onTapTrackStart() { + _isShiftPressed = HardwareKeyboard.instance.logicalKeysPressed + .intersection({ + LogicalKeyboardKey.shiftLeft, + LogicalKeyboardKey.shiftRight + }).isNotEmpty; + } + + /// Handler for [TextSelectionGestureDetector.onTapTrackReset]. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onTapTrackReset], which triggers this + /// callback. + @protected + void onTapTrackReset() { + _isShiftPressed = false; + } + + /// Handler for [TextSelectionGestureDetector.onTapDown]. + /// + /// By default, it forwards the tap to [RenderEditable.handleTapDown] and sets + /// [shouldShowSelectionToolbar] to true if the tap was initiated by a finger or stylus. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onTapDown], which triggers this callback. + @protected + void onTapDown(TapDragDownDetails details) { + if (!delegate.selectionEnabled) { + return; + } + // TODO(Renzo-Olivares): Migrate text selection gestures away from saving state + // in renderEditable. The gesture callbacks can use the details objects directly + // in callbacks variants that provide them [TapGestureRecognizer.onSecondaryTap] + // vs [TapGestureRecognizer.onSecondaryTapUp] instead of having to track state in + // renderEditable. When this migration is complete we should remove this hack. + // See https://github.com/flutter/flutter/issues/115130. + renderEditable + .handleTapDown(TapDownDetails(globalPosition: details.globalPosition)); + // The selection overlay should only be shown when the user is interacting + // through a touch screen (via either a finger or a stylus). A mouse shouldn't + // trigger the selection overlay. + // For backwards-compatibility, we treat a null kind the same as touch. + final PointerDeviceKind? kind = details.kind; + // TODO(justinmc): Should a desktop platform show its selection toolbar when + // receiving a tap event? Say a Windows device with a touchscreen. + // https://github.com/flutter/flutter/issues/106586 + _shouldShowSelectionToolbar = kind == null || + kind == PointerDeviceKind.touch || + kind == PointerDeviceKind.stylus; + + // It is impossible to extend the selection when the shift key is pressed, if the + // renderEditable.selection is invalid. + final bool isShiftPressedValid = + _isShiftPressed && renderEditable.selection?.baseOffset != null; + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + // On mobile platforms the selection is set on tap up. + break; + case TargetPlatform.macOS: + editableText.hideToolbar(); + // On macOS, a shift-tapped unfocused field expands from 0, not from the + // previous selection. + if (isShiftPressedValid) { + final TextSelection? fromSelection = renderEditable.hasFocus + ? null + : const TextSelection.collapsed(offset: 0); + _expandSelection( + details.globalPosition, + SelectionChangedCause.tap, + fromSelection, + ); + return; + } + // On macOS, a tap/click places the selection in a precise position. + // This differs from iOS/iPadOS, where if the gesture is done by a touch + // then the selection moves to the closest word edge, instead of a + // precise position. + renderEditable.selectPosition(cause: SelectionChangedCause.tap); + case TargetPlatform.linux: + case TargetPlatform.windows: + editableText.hideToolbar(); + if (isShiftPressedValid) { + _extendSelection(details.globalPosition, SelectionChangedCause.tap); + return; + } + renderEditable.selectPosition(cause: SelectionChangedCause.tap); + } + } + + /// Handler for [TextSelectionGestureDetector.onForcePressStart]. + /// + /// By default, it selects the word at the position of the force press, + /// if selection is enabled. + /// + /// This callback is only applicable when force press is enabled. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onForcePressStart], which triggers this + /// callback. + @protected + void onForcePressStart(ForcePressDetails details) { + assert(delegate.forcePressEnabled); + _shouldShowSelectionToolbar = true; + if (delegate.selectionEnabled) { + renderEditable.selectWordsInRange( + from: details.globalPosition, + cause: SelectionChangedCause.forcePress, + ); + } + } + + /// Handler for [TextSelectionGestureDetector.onForcePressEnd]. + /// + /// By default, it selects words in the range specified in [details] and shows + /// toolbar if it is necessary. + /// + /// This callback is only applicable when force press is enabled. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onForcePressEnd], which triggers this + /// callback. + @protected + void onForcePressEnd(ForcePressDetails details) { + assert(delegate.forcePressEnabled); + renderEditable.selectWordsInRange( + from: details.globalPosition, + cause: SelectionChangedCause.forcePress, + ); + if (shouldShowSelectionToolbar) { + editableText.showToolbar(); + } + } + + /// Whether the provided [onUserTap] callback should be dispatched on every + /// tap or only non-consecutive taps. + /// + /// Defaults to false. + @protected + bool get onUserTapAlwaysCalled => false; + + /// Handler for [TextSelectionGestureDetector.onUserTap]. + /// + /// By default, it serves as placeholder to enable subclass override. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onUserTap], which triggers this + /// callback. + /// * [TextSelectionGestureDetector.onUserTapAlwaysCalled], which controls + /// whether this callback is called only on the first tap in a series + /// of taps. + @protected + void onUserTap() {/* Subclass should override this method if needed. */} + + /// Handler for [TextSelectionGestureDetector.onSingleTapUp]. + /// + /// By default, it selects word edge if selection is enabled. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onSingleTapUp], which triggers + /// this callback. + @protected + void onSingleTapUp(TapDragUpDetails details) { + if (delegate.selectionEnabled) { + // It is impossible to extend the selection when the shift key is pressed, if the + // renderEditable.selection is invalid. + final bool isShiftPressedValid = + _isShiftPressed && renderEditable.selection?.baseOffset != null; + switch (defaultTargetPlatform) { + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + break; + // On desktop platforms the selection is set on tap down. + case TargetPlatform.android: + editableText.hideToolbar(false); + if (isShiftPressedValid) { + _extendSelection(details.globalPosition, SelectionChangedCause.tap); + return; + } + renderEditable.selectPosition(cause: SelectionChangedCause.tap); + editableText.showSpellCheckSuggestionsToolbar(); + case TargetPlatform.fuchsia: + editableText.hideToolbar(false); + if (isShiftPressedValid) { + _extendSelection(details.globalPosition, SelectionChangedCause.tap); + return; + } + renderEditable.selectPosition(cause: SelectionChangedCause.tap); + case TargetPlatform.iOS: + if (isShiftPressedValid) { + // On iOS, a shift-tapped unfocused field expands from 0, not from + // the previous selection. + final TextSelection? fromSelection = renderEditable.hasFocus + ? null + : const TextSelection.collapsed(offset: 0); + _expandSelection( + details.globalPosition, + SelectionChangedCause.tap, + fromSelection, + ); + return; + } + switch (details.kind) { + case PointerDeviceKind.mouse: + case PointerDeviceKind.trackpad: + case PointerDeviceKind.stylus: + case PointerDeviceKind.invertedStylus: + // TODO(camsim99): Determine spell check toolbar behavior in these cases: + // https://github.com/flutter/flutter/issues/119573. + // Precise devices should place the cursor at a precise position if the + // word at the text position is not misspelled. + renderEditable.selectPosition(cause: SelectionChangedCause.tap); + case PointerDeviceKind.touch: + case PointerDeviceKind.unknown: + // If the word that was tapped is misspelled, select the word and show the spell check suggestions + // toolbar once. If additional taps are made on a misspelled word, toggle the toolbar. If the word + // is not misspelled, default to the following behavior: + // + // Toggle the toolbar if the `previousSelection` is collapsed, the tap is on the selection, the + // TextAffinity remains the same, and the editable is focused. The TextAffinity is important when the + // cursor is on the boundary of a line wrap, if the affinity is different (i.e. it is downstream), the + // selection should move to the following line and not toggle the toolbar. + // + // Toggle the toolbar when the tap is exclusively within the bounds of a non-collapsed `previousSelection`, + // and the editable is focused. + // + // Selects the word edge closest to the tap when the editable is not focused, or if the tap was neither exclusively + // or inclusively on `previousSelection`. If the selection remains the same after selecting the word edge, then we + // toggle the toolbar. If the selection changes then we hide the toolbar. + final TextSelection previousSelection = + renderEditable.selection ?? + editableText.textEditingValue.selection; + final TextPosition textPosition = + renderEditable.getPositionForPoint(details.globalPosition); + final bool isAffinityTheSame = + textPosition.affinity == previousSelection.affinity; + final bool wordAtCursorIndexIsMisspelled = editableText + .findSuggestionSpanAtCursorIndex(textPosition.offset) != + null; + + if (wordAtCursorIndexIsMisspelled) { + renderEditable.selectWord(cause: SelectionChangedCause.tap); + if (previousSelection != + editableText.textEditingValue.selection) { + editableText.showSpellCheckSuggestionsToolbar(); + } else { + editableText.toggleToolbar(false); + } + } else if (((_positionWasOnSelectionExclusive(textPosition) && + !previousSelection.isCollapsed) || + (_positionWasOnSelectionInclusive(textPosition) && + previousSelection.isCollapsed && + isAffinityTheSame)) && + renderEditable.hasFocus) { + editableText.toggleToolbar(false); + } else { + renderEditable.selectWordEdge(cause: SelectionChangedCause.tap); + if (previousSelection == + editableText.textEditingValue.selection && + renderEditable.hasFocus) { + editableText.toggleToolbar(false); + } else { + editableText.hideToolbar(false); + } + } + } + } + } + editableText.requestKeyboard(); + } + + /// Handler for [TextSelectionGestureDetector.onSingleTapCancel]. + /// + /// By default, it serves as placeholder to enable subclass override. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onSingleTapCancel], which triggers + /// this callback. + @protected + void onSingleTapCancel() { + /* Subclass should override this method if needed. */ + } + + /// Handler for [TextSelectionGestureDetector.onSingleLongTapStart]. + /// + /// By default, it selects text position specified in [details] if selection + /// is enabled. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onSingleLongTapStart], which triggers + /// this callback. + @protected + void onSingleLongTapStart(LongPressStartDetails details) { + if (delegate.selectionEnabled) { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + if (!renderEditable.hasFocus) { + _longPressStartedWithoutFocus = true; + renderEditable.selectWord(cause: SelectionChangedCause.longPress); + } else { + renderEditable.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.longPress, + ); + // Show the floating cursor. + final RawFloatingCursorPoint cursorPoint = RawFloatingCursorPoint( + state: FloatingCursorDragState.Start, + startLocation: ( + renderEditable.globalToLocal(details.globalPosition), + TextPosition( + offset: editableText.textEditingValue.selection.baseOffset, + affinity: editableText.textEditingValue.selection.affinity, + ), + ), + offset: Offset.zero, + ); + editableText.updateFloatingCursor(cursorPoint); + } + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + renderEditable.selectWord(cause: SelectionChangedCause.longPress); + } + + _showMagnifierIfSupportedByPlatform(details.globalPosition); + + _dragStartViewportOffset = renderEditable.offset.pixels; + _dragStartScrollOffset = _scrollPosition; + } + } + + /// Handler for [TextSelectionGestureDetector.onSingleLongTapMoveUpdate]. + /// + /// By default, it updates the selection location specified in [details] if + /// selection is enabled. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onSingleLongTapMoveUpdate], which + /// triggers this callback. + @protected + void onSingleLongTapMoveUpdate(LongPressMoveUpdateDetails details) { + if (delegate.selectionEnabled) { + // Adjust the drag start offset for possible viewport offset changes. + final Offset editableOffset = renderEditable.maxLines == 1 + ? Offset(renderEditable.offset.pixels - _dragStartViewportOffset, 0.0) + : Offset( + 0.0, renderEditable.offset.pixels - _dragStartViewportOffset); + final Offset scrollableOffset = + switch (axisDirectionToAxis(_scrollDirection ?? AxisDirection.left)) { + Axis.horizontal => + Offset(_scrollPosition - _dragStartScrollOffset, 0.0), + Axis.vertical => Offset(0.0, _scrollPosition - _dragStartScrollOffset), + }; + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + if (_longPressStartedWithoutFocus) { + renderEditable.selectWordsInRange( + from: details.globalPosition - + details.offsetFromOrigin - + editableOffset - + scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.longPress, + ); + } else { + renderEditable.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.longPress, + ); + // Update the floating cursor. + final RawFloatingCursorPoint cursorPoint = RawFloatingCursorPoint( + state: FloatingCursorDragState.Update, + offset: details.offsetFromOrigin, + ); + editableText.updateFloatingCursor(cursorPoint); + } + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + renderEditable.selectWordsInRange( + from: details.globalPosition - + details.offsetFromOrigin - + editableOffset - + scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.longPress, + ); + } + + _showMagnifierIfSupportedByPlatform(details.globalPosition); + } + } + + /// Handler for [TextSelectionGestureDetector.onSingleLongTapEnd]. + /// + /// By default, it shows toolbar if necessary. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onSingleLongTapEnd], which triggers this + /// callback. + @protected + void onSingleLongTapEnd(LongPressEndDetails details) { + _hideMagnifierIfSupportedByPlatform(); + if (shouldShowSelectionToolbar) { + editableText.showToolbar(); + } + _longPressStartedWithoutFocus = false; + _dragStartViewportOffset = 0.0; + _dragStartScrollOffset = 0.0; + if (defaultTargetPlatform == TargetPlatform.iOS && + delegate.selectionEnabled && + editableText.textEditingValue.selection.isCollapsed) { + // Update the floating cursor. + final RawFloatingCursorPoint cursorPoint = + RawFloatingCursorPoint(state: FloatingCursorDragState.End); + editableText.updateFloatingCursor(cursorPoint); + } + } + + /// Handler for [TextSelectionGestureDetector.onSecondaryTap]. + /// + /// By default, selects the word if possible and shows the toolbar. + @protected + void onSecondaryTap() { + if (!delegate.selectionEnabled) { + return; + } + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + if (!_lastSecondaryTapWasOnSelection || !renderEditable.hasFocus) { + renderEditable.selectWord(cause: SelectionChangedCause.tap); + } + if (shouldShowSelectionToolbar) { + editableText.hideToolbar(); + editableText.showToolbar(); + } + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + if (!renderEditable.hasFocus) { + renderEditable.selectPosition(cause: SelectionChangedCause.tap); + } + editableText.toggleToolbar(); + } + } + + /// Handler for [TextSelectionGestureDetector.onSecondaryTapDown]. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onSecondaryTapDown], which triggers this + /// callback. + /// * [onSecondaryTap], which is typically called after this. + @protected + void onSecondaryTapDown(TapDownDetails details) { + // TODO(Renzo-Olivares): Migrate text selection gestures away from saving state + // in renderEditable. The gesture callbacks can use the details objects directly + // in callbacks variants that provide them [TapGestureRecognizer.onSecondaryTap] + // vs [TapGestureRecognizer.onSecondaryTapUp] instead of having to track state in + // renderEditable. When this migration is complete we should remove this hack. + // See https://github.com/flutter/flutter/issues/115130. + renderEditable.handleSecondaryTapDown( + TapDownDetails(globalPosition: details.globalPosition)); + _shouldShowSelectionToolbar = true; + } + + /// Handler for [TextSelectionGestureDetector.onDoubleTapDown]. + /// + /// By default, it selects a word through [RenderEditable.selectWord] if + /// selectionEnabled and shows toolbar if necessary. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onDoubleTapDown], which triggers this + /// callback. + @protected + void onDoubleTapDown(TapDragDownDetails details) { + if (delegate.selectionEnabled) { + renderEditable.selectWord(cause: SelectionChangedCause.doubleTap); + if (shouldShowSelectionToolbar) { + editableText.showToolbar(); + } + } + } + + // Selects the set of paragraphs in a document that intersect a given range of + // global positions. + void _selectParagraphsInRange( + {required Offset from, Offset? to, SelectionChangedCause? cause}) { + final TextBoundary paragraphBoundary = + ParagraphBoundary(editableText.textEditingValue.text); + _selectTextBoundariesInRange( + boundary: paragraphBoundary, from: from, to: to, cause: cause); + } + + // Selects the set of lines in a document that intersect a given range of + // global positions. + void _selectLinesInRange( + {required Offset from, Offset? to, SelectionChangedCause? cause}) { + final TextBoundary lineBoundary = LineBoundary(renderEditable); + _selectTextBoundariesInRange( + boundary: lineBoundary, from: from, to: to, cause: cause); + } + + // Returns the location of a text boundary at `extent`. When `extent` is at + // the end of the text, returns the previous text boundary's location. + TextRange _moveToTextBoundary( + TextPosition extent, TextBoundary textBoundary) { + assert(extent.offset >= 0); + // Use extent.offset - 1 when `extent` is at the end of the text to retrieve + // the previous text boundary's location. + final int start = textBoundary.getLeadingTextBoundaryAt( + extent.offset == editableText.textEditingValue.text.length + ? extent.offset - 1 + : extent.offset) ?? + 0; + final int end = textBoundary.getTrailingTextBoundaryAt(extent.offset) ?? + editableText.textEditingValue.text.length; + return TextRange(start: start, end: end); + } + + // Selects the set of text boundaries in a document that intersect a given + // range of global positions. + // + // The set of text boundaries selected are not strictly bounded by the range + // of global positions. + // + // The first and last endpoints of the selection will always be at the + // beginning and end of a text boundary respectively. + void _selectTextBoundariesInRange( + {required TextBoundary boundary, + required Offset from, + Offset? to, + SelectionChangedCause? cause}) { + final TextPosition fromPosition = renderEditable.getPositionForPoint(from); + final TextRange fromRange = _moveToTextBoundary(fromPosition, boundary); + final TextPosition toPosition = + to == null ? fromPosition : renderEditable.getPositionForPoint(to); + final TextRange toRange = toPosition == fromPosition + ? fromRange + : _moveToTextBoundary(toPosition, boundary); + final bool isFromBoundaryBeforeToBoundary = fromRange.start < toRange.end; + + final TextSelection newSelection = isFromBoundaryBeforeToBoundary + ? TextSelection(baseOffset: fromRange.start, extentOffset: toRange.end) + : TextSelection(baseOffset: fromRange.end, extentOffset: toRange.start); + + editableText.userUpdateTextEditingValue( + editableText.textEditingValue.copyWith(selection: newSelection), + cause, + ); + } + + /// Handler for [TextSelectionGestureDetector.onTripleTapDown]. + /// + /// By default, it selects a paragraph if + /// [TextSelectionGestureDetectorBuilderDelegate.selectionEnabled] is true + /// and shows the toolbar if necessary. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onTripleTapDown], which triggers this + /// callback. + @protected + void onTripleTapDown(TapDragDownDetails details) { + if (!delegate.selectionEnabled) { + return; + } + if (renderEditable.maxLines == 1) { + editableText.selectAll(SelectionChangedCause.tap); + } else { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + case TargetPlatform.macOS: + case TargetPlatform.windows: + _selectParagraphsInRange( + from: details.globalPosition, cause: SelectionChangedCause.tap); + case TargetPlatform.linux: + _selectLinesInRange( + from: details.globalPosition, cause: SelectionChangedCause.tap); + } + } + if (shouldShowSelectionToolbar) { + editableText.showToolbar(); + } + } + + /// Handler for [TextSelectionGestureDetector.onDragSelectionStart]. + /// + /// By default, it selects a text position specified in [details]. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onDragSelectionStart], which triggers + /// this callback. + @protected + void onDragSelectionStart(TapDragStartDetails details) { + if (!delegate.selectionEnabled) { + return; + } + final PointerDeviceKind? kind = details.kind; + _shouldShowSelectionToolbar = kind == null || + kind == PointerDeviceKind.touch || + kind == PointerDeviceKind.stylus; + + _dragStartSelection = renderEditable.selection; + _dragStartScrollOffset = _scrollPosition; + _dragStartViewportOffset = renderEditable.offset.pixels; + + if (_TextSelectionGestureDetectorState._getEffectiveConsecutiveTapCount( + details.consecutiveTapCount) > + 1) { + // Do not set the selection on a consecutive tap and drag. + return; + } + + if (_isShiftPressed && + renderEditable.selection != null && + renderEditable.selection!.isValid) { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + _expandSelection(details.globalPosition, SelectionChangedCause.drag); + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + _extendSelection(details.globalPosition, SelectionChangedCause.drag); + } + } else { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + switch (details.kind) { + case PointerDeviceKind.mouse: + case PointerDeviceKind.trackpad: + renderEditable.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + case PointerDeviceKind.stylus: + case PointerDeviceKind.invertedStylus: + case PointerDeviceKind.touch: + case PointerDeviceKind.unknown: + case null: + } + case TargetPlatform.android: + case TargetPlatform.fuchsia: + switch (details.kind) { + case PointerDeviceKind.mouse: + case PointerDeviceKind.trackpad: + renderEditable.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + case PointerDeviceKind.stylus: + case PointerDeviceKind.invertedStylus: + case PointerDeviceKind.touch: + case PointerDeviceKind.unknown: + // For Android, Fuchsia, and iOS platforms, a touch drag + // does not initiate unless the editable has focus. + if (renderEditable.hasFocus) { + renderEditable.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + _showMagnifierIfSupportedByPlatform(details.globalPosition); + } + case null: + } + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + renderEditable.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + } + } + } + + /// Handler for [TextSelectionGestureDetector.onDragSelectionUpdate]. + /// + /// By default, it updates the selection location specified in the provided + /// details objects. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onDragSelectionUpdate], which triggers + /// this callback./lib/src/material/text_field.dart + @protected + void onDragSelectionUpdate(TapDragUpdateDetails details) { + if (!delegate.selectionEnabled) { + return; + } + + if (!_isShiftPressed) { + // Adjust the drag start offset for possible viewport offset changes. + final Offset editableOffset = renderEditable.maxLines == 1 + ? Offset(renderEditable.offset.pixels - _dragStartViewportOffset, 0.0) + : Offset( + 0.0, renderEditable.offset.pixels - _dragStartViewportOffset); + final Offset scrollableOffset = + switch (axisDirectionToAxis(_scrollDirection ?? AxisDirection.left)) { + Axis.horizontal => + Offset(_scrollPosition - _dragStartScrollOffset, 0.0), + Axis.vertical => Offset(0.0, _scrollPosition - _dragStartScrollOffset), + }; + final Offset dragStartGlobalPosition = + details.globalPosition - details.offsetFromOrigin; + + // Select word by word. + if (_TextSelectionGestureDetectorState._getEffectiveConsecutiveTapCount( + details.consecutiveTapCount) == + 2) { + renderEditable.selectWordsInRange( + from: dragStartGlobalPosition - editableOffset - scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + + switch (details.kind) { + case PointerDeviceKind.stylus: + case PointerDeviceKind.invertedStylus: + case PointerDeviceKind.touch: + case PointerDeviceKind.unknown: + return _showMagnifierIfSupportedByPlatform(details.globalPosition); + case PointerDeviceKind.mouse: + case PointerDeviceKind.trackpad: + case null: + return; + } + } + + // Select paragraph-by-paragraph. + if (_TextSelectionGestureDetectorState._getEffectiveConsecutiveTapCount( + details.consecutiveTapCount) == + 3) { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + switch (details.kind) { + case PointerDeviceKind.mouse: + case PointerDeviceKind.trackpad: + return _selectParagraphsInRange( + from: dragStartGlobalPosition - + editableOffset - + scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + case PointerDeviceKind.stylus: + case PointerDeviceKind.invertedStylus: + case PointerDeviceKind.touch: + case PointerDeviceKind.unknown: + case null: + // Triple tap to drag is not present on these platforms when using + // non-precise pointer devices at the moment. + break; + } + return; + case TargetPlatform.linux: + return _selectLinesInRange( + from: dragStartGlobalPosition - editableOffset - scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + case TargetPlatform.windows: + case TargetPlatform.macOS: + return _selectParagraphsInRange( + from: dragStartGlobalPosition - editableOffset - scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + } + } + + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + // With a mouse device, a drag should select the range from the origin of the drag + // to the current position of the drag. + // + // With a touch device, nothing should happen. + switch (details.kind) { + case PointerDeviceKind.mouse: + case PointerDeviceKind.trackpad: + return renderEditable.selectPositionAt( + from: + dragStartGlobalPosition - editableOffset - scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + case PointerDeviceKind.stylus: + case PointerDeviceKind.invertedStylus: + case PointerDeviceKind.touch: + case PointerDeviceKind.unknown: + case null: + break; + } + return; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + // With a precise pointer device, such as a mouse, trackpad, or stylus, + // the drag will select the text spanning the origin of the drag to the end of the drag. + // With a touch device, the cursor should move with the drag. + switch (details.kind) { + case PointerDeviceKind.mouse: + case PointerDeviceKind.trackpad: + case PointerDeviceKind.stylus: + case PointerDeviceKind.invertedStylus: + return renderEditable.selectPositionAt( + from: + dragStartGlobalPosition - editableOffset - scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + case PointerDeviceKind.touch: + case PointerDeviceKind.unknown: + if (renderEditable.hasFocus) { + renderEditable.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + return _showMagnifierIfSupportedByPlatform( + details.globalPosition); + } + case null: + break; + } + return; + case TargetPlatform.macOS: + case TargetPlatform.linux: + case TargetPlatform.windows: + return renderEditable.selectPositionAt( + from: dragStartGlobalPosition - editableOffset - scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + } + } + + if (_dragStartSelection!.isCollapsed || + (defaultTargetPlatform != TargetPlatform.iOS && + defaultTargetPlatform != TargetPlatform.macOS)) { + return _extendSelection( + details.globalPosition, SelectionChangedCause.drag); + } + + // If the drag inverts the selection, Mac and iOS revert to the initial + // selection. + final TextSelection selection = editableText.textEditingValue.selection; + final TextPosition nextExtent = + renderEditable.getPositionForPoint(details.globalPosition); + final bool isShiftTapDragSelectionForward = + _dragStartSelection!.baseOffset < _dragStartSelection!.extentOffset; + final bool isInverted = isShiftTapDragSelectionForward + ? nextExtent.offset < _dragStartSelection!.baseOffset + : nextExtent.offset > _dragStartSelection!.baseOffset; + if (isInverted && selection.baseOffset == _dragStartSelection!.baseOffset) { + editableText.userUpdateTextEditingValue( + editableText.textEditingValue.copyWith( + selection: TextSelection( + baseOffset: _dragStartSelection!.extentOffset, + extentOffset: nextExtent.offset, + ), + ), + SelectionChangedCause.drag, + ); + } else if (!isInverted && + nextExtent.offset != _dragStartSelection!.baseOffset && + selection.baseOffset != _dragStartSelection!.baseOffset) { + editableText.userUpdateTextEditingValue( + editableText.textEditingValue.copyWith( + selection: TextSelection( + baseOffset: _dragStartSelection!.baseOffset, + extentOffset: nextExtent.offset, + ), + ), + SelectionChangedCause.drag, + ); + } else { + _extendSelection(details.globalPosition, SelectionChangedCause.drag); + } + } + + /// Handler for [TextSelectionGestureDetector.onDragSelectionEnd]. + /// + /// By default, it cleans up the state used for handling certain + /// built-in behaviors. + /// + /// See also: + /// + /// * [TextSelectionGestureDetector.onDragSelectionEnd], which triggers this + /// callback. + @protected + void onDragSelectionEnd(TapDragEndDetails details) { + if (_shouldShowSelectionToolbar && + _TextSelectionGestureDetectorState._getEffectiveConsecutiveTapCount( + details.consecutiveTapCount) == + 2) { + editableText.showToolbar(); + } + + if (_isShiftPressed) { + _dragStartSelection = null; + } + + _hideMagnifierIfSupportedByPlatform(); + } + + /// Returns a [TextSelectionGestureDetector] configured with the handlers + /// provided by this builder. + /// + /// The [child] or its subtree should contain an [EditableText] whose key is + /// the [GlobalKey] provided by the [delegate]'s + /// [TextSelectionGestureDetectorBuilderDelegate.editableTextKey]. + Widget buildGestureDetector({ + Key? key, + HitTestBehavior? behavior, + required Widget child, + }) { + return TextSelectionGestureDetector( + key: key, + onTapTrackStart: onTapTrackStart, + onTapTrackReset: onTapTrackReset, + onTapDown: onTapDown, + onForcePressStart: delegate.forcePressEnabled ? onForcePressStart : null, + onForcePressEnd: delegate.forcePressEnabled ? onForcePressEnd : null, + onSecondaryTap: onSecondaryTap, + onSecondaryTapDown: onSecondaryTapDown, + onSingleTapUp: onSingleTapUp, + onSingleTapCancel: onSingleTapCancel, + onUserTap: onUserTap, + onSingleLongTapStart: onSingleLongTapStart, + onSingleLongTapMoveUpdate: onSingleLongTapMoveUpdate, + onSingleLongTapEnd: onSingleLongTapEnd, + onDoubleTapDown: onDoubleTapDown, + onTripleTapDown: onTripleTapDown, + onDragSelectionStart: onDragSelectionStart, + onDragSelectionUpdate: onDragSelectionUpdate, + onDragSelectionEnd: onDragSelectionEnd, + onUserTapAlwaysCalled: onUserTapAlwaysCalled, + behavior: behavior, + child: child, + ); + } +} + +class _TextSelectionGestureDetectorState { + _TextSelectionGestureDetectorState._(); + // Converts the details.consecutiveTapCount from a TapAndDrag*Details object, + // which can grow to be infinitely large, to a value between 1 and 3. The value + // that the raw count is converted to is based on the default observed behavior + // on the native platforms. + // + // This method should be used in all instances when details.consecutiveTapCount + // would be used. + static int _getEffectiveConsecutiveTapCount(int rawCount) { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + // From observation, these platform's reset their tap count to 0 when + // the number of consecutive taps exceeds 3. For example on Debian Linux + // with GTK, when going past a triple click, on the fourth click the + // selection is moved to the precise click position, on the fifth click + // the word at the position is selected, and on the sixth click the + // paragraph at the position is selected. + return rawCount <= 3 + ? rawCount + : (rawCount % 3 == 0 ? 3 : rawCount % 3); + case TargetPlatform.iOS: + case TargetPlatform.macOS: + // From observation, these platform's either hold their tap count at 3. + // For example on macOS, when going past a triple click, the selection + // should be retained at the paragraph that was first selected on triple + // click. + return math.min(rawCount, 3); + case TargetPlatform.windows: + // From observation, this platform's consecutive tap actions alternate + // between double click and triple click actions. For example, after a + // triple click has selected a paragraph, on the next click the word at + // the clicked position will be selected, and on the next click the + // paragraph at the position is selected. + return rawCount < 2 ? rawCount : 2 + rawCount % 2; + } + } +} diff --git a/local_packages/extended_text_field-16.0.2/pubspec.yaml b/local_packages/extended_text_field-16.0.2/pubspec.yaml new file mode 100644 index 0000000..4ec32c5 --- /dev/null +++ b/local_packages/extended_text_field-16.0.2/pubspec.yaml @@ -0,0 +1,66 @@ +name: extended_text_field +description: Extended official text field to build special text like inline image, @somebody, custom background etc quickly.It also support to build custom seleciton toolbar and handles. +version: 16.0.2 +repository: https://github.com/fluttercandies/extended_text_field +issue_tracker: https://github.com/fluttercandies/extended_text_field/issues +topics: + - extended-text-field + - rich-text + +environment: + sdk: '>=3.5.0 <4.0.0' + flutter: ">=3.24.0" +dependencies: + + extended_text_library: ^12.0.0 + # extended_text_library: + # version: ^11.0.0-dev.1 + # hosted: "https://pub.dev" + flutter: + sdk: flutter +dev_dependencies: + flutter_test: + sdk: flutter + +dependency_overrides: + #extended_text_library: + # git: + # url: https://github.com/fluttercandies/extended_text_library.git + # ref: refactor + #path: ../extended_text_library +# For information on the generic Dart part of this file, see the +# following page: https://www.dartlang.org/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.io/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.io/assets-and-images/#resolution-aware. + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.io/custom-fonts/#from-packages diff --git a/local_packages/flutter_foreground_task-9.1.0/android/build.gradle b/local_packages/flutter_foreground_task-9.1.0/android/build.gradle index bafdc15..4428033 100644 --- a/local_packages/flutter_foreground_task-9.1.0/android/build.gradle +++ b/local_packages/flutter_foreground_task-9.1.0/android/build.gradle @@ -2,14 +2,14 @@ group 'com.pravera.flutter_foreground_task' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '2.1.0' + ext.kotlin_version = '2.2.20' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.6.0' + classpath 'com.android.tools.build:gradle:8.11.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/local_packages/image_cropper-5.0.1/CHANGELOG.md b/local_packages/image_cropper-5.0.1/CHANGELOG.md new file mode 100644 index 0000000..7aea371 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/CHANGELOG.md @@ -0,0 +1,283 @@ +## 5.0.1 + +* Android: improve compression quality + +## 5.0.0 + +* upgrade `http` to v1.0.0 + +## 4.0.1 + +* refactor CHANGELOG + +## 4.0.0 + +* Android: support namespace, Gradle 8 + +## 3.0.3 + +* iOS: bump ios minimum version to 11 + +## 3.0.2 + +* Web: fix web translation issue + +## 3.0.1 + +* Android: replace `jcenter` by `mavenCentral` +* Web: support localization + +## 3.0.0 + +* **BREAKING CHANGE**: move all setting models to platform interface packages. +* Separate Javascript codes from `WebSettings` model (so there's no need to use conditional import to config Web UI now) + +### ***MIGRATION GUIDE*** + +#### **BEFORE** + +```dart +WebUiSettings( + boundary: Boundary( + width: 520, + height: 520, + ), + viewPort: ViewPort(width: 480, height: 480, type: 'circle'), +) +``` + +#### **AFTER** +```dart +WebUiSettings( + boundary: const CroppieBoundary( + width: 520, + height: 520, + ), + viewPort: const CroppieViewPort(width: 480, height: 480, type: 'circle'), +) +``` + +## 2.0.3 + +* correct importing JS library + +## 2.0.2 + +* update document + +## 2.0.1 + +* fix missing pubspec config on web + +## 2.0.0 + +* Support Web +* **BREAKING CHANGE**: + - change result data type from `File` to `CroppedFile`. + - remove `androidUiSettings` and `iosUiSettings` parameter in `cropImage` method, they are replaced by `uiSettings` + +### ***MIGRATION GUIDE*** + +#### **BEFORE** +```dart +File croppedFile = await ImageCropper().cropImage( + sourcePath: imageFile.path, + androidUiSettings: AndroidUiSettings( + toolbarTitle: 'Cropper', + toolbarColor: Colors.deepOrange, + toolbarWidgetColor: Colors.white, + initAspectRatio: CropAspectRatioPreset.original, + lockAspectRatio: false), + iosUiSettings: IOSUiSettings( + minimumAspectRatio: 1.0, + ) + ); +``` + +#### **AFTER** +```dart +CroppedFile croppedFile = await ImageCropper().cropImage( + sourcePath: imageFile.path, + uiSettings: [ + AndroidUiSettings( + toolbarTitle: 'Cropper', + toolbarColor: Colors.deepOrange, + toolbarWidgetColor: Colors.white, + initAspectRatio: CropAspectRatioPreset.original, + lockAspectRatio: false), + IOSUiSettings( + title: 'Cropper', + ) + ], + ); +``` + +## 2.0.0-beta.3 + +* support rotate image on Web + +## 2.0.0-beta.2 + +* correct missing README document about Web support. + +## 2.0.0-beta.1 + +* **BREAKING CHANGE**: update result models to support web, replace `File` by `CroppedFile` + +## 2.0.0-beta + +* migrate to federated plugins +* **BREAKING CHANGE**: remove `androidUiSettings` and `iosUiSettings`, they are replaced by `uiSettings` for sake of supporting multiple platforms in future. + +### ***MIGRATION GUIDE*** + +#### **BEFORE** +```dart +File croppedFile = await ImageCropper().cropImage( + sourcePath: imageFile.path, + androidUiSettings: AndroidUiSettings( + toolbarTitle: 'Cropper', + toolbarColor: Colors.deepOrange, + toolbarWidgetColor: Colors.white, + initAspectRatio: CropAspectRatioPreset.original, + lockAspectRatio: false), + iosUiSettings: IOSUiSettings( + minimumAspectRatio: 1.0, + ) + ); +``` + +#### **AFTER** +```dart +File croppedFile = await ImageCropper().cropImage( + sourcePath: imageFile.path, + uiSettings: [ + AndroidUiSettings( + toolbarTitle: 'Cropper', + toolbarColor: Colors.deepOrange, + toolbarWidgetColor: Colors.white, + initAspectRatio: CropAspectRatioPreset.original, + lockAspectRatio: false), + IOSUiSettings( + title: 'Cropper', + ) + ], + ); +``` + +## 1.5.1 + +* update README to introduce Web support experiment + +## 1.5.0 + +* Upgrade `uCrop` to v2.2.8 +* Upgrade `TOCropViewController` to v2.6.1 +* PR: [#309](https://github.com/hnvn/flutter_image_cropper/pull/309), #[229](https://github.com/hnvn/flutter_image_cropper/pull/299), #[294](https://github.com/hnvn/flutter_image_cropper/pull/294), [#258](https://github.com/hnvn/flutter_image_cropper/pull/258), [#251](https://github.com/hnvn/flutter_image_cropper/pull/258) +* **BREAKING CHANGE**: change `cropImage()` to instance method + +## 1.4.1 + +* Upgrade `uCrop` to v2.2.7 + +## 1.4.0 + +* Upgrade `TOCropViewController` to v2.6.0 +* Migrate to nullsafety + +## 1.3.1 + +* Upgrade `uCrop` to v2.2.6 +* Fix bug: image compress file format (png) + +## 1.3.0 + +* Update `pubspec` to new format +* Upgrade `TOCropViewController` to v2.5.4 + +## 1.2.3 + +* Android: fix bug + +## 1.2.2 + +* Android: upgrade uCrop to v2.2.5 +* **BREAKING CHANGE**: remove `activeWidgetColor` from `AndroidUiSettings` + +## 1.2.1 + +* iOS: add more UI customization properties (title, initRect) +* update Flutter version constraint (>= v1.12.13) + +## 1.2.0 + +* Android: migrate to embedding v2 + +## 1.1.2 + +* Android: fix bug crashing on Flutter v1.12.13 + +## 1.1.1 + +* Android: upgrade gradle version +* iOS: remove `static_framework` Pod configuration, upgrade `TOCropViewController` to 2.5.2 + +## 1.1.0 + +* **BIG UPDATES**: supports UI customization on both of Android and iOS, supports more image compress format and quality. +* **BREAKING CHANGE**: `ratioX` and `ratioY` are replaced by `aspectRatio`, `circleShape` is replaced by `cropStyle`, removed `toolbarTitle` and `toolbarColor` (these properties are moved into `AndroidUiSettings`) +* iOS: upgrade native library (TOCropViewController v2.5.1) +* Android: upgrade native library (uCrop v2.2.4) + +## 1.0.2 + +* iOS: upgrade native library +* Android: fix bug #40 + +## 1.0.1 + +* Android: migrate to AndroidX +* upgrade native libraries + +## 1.0.0 + +* Android: remove deprecated support libraries +* Android: target version to 28 + +## 0.0.9 + +* clarify code document + +## 0.0.8 + +* set default value for `circleShape` + +## 0.0.7 + +* support circular cropping + +## 0.0.6 + +* upgrade `TOCropViewController` dependency to v2.3.8 + +## 0.0.5 + +* re-config to support Dart2 +* fix bug: lock aspect ratio + +## 0.0.4 + +* refactor: change `toolbarColor` type of `int` to `Color` + +## 0.0.3 + +* fix bug: increasing image size after cropping +* add new feature: `toolbarTitle` and `toolbarColor` to customize title and color of copper `Activity` + +## 0.0.2 + +* clarify document + +## 0.0.1 + +* initial release. \ No newline at end of file diff --git a/local_packages/image_cropper-5.0.1/LICENSE b/local_packages/image_cropper-5.0.1/LICENSE new file mode 100644 index 0000000..2db5f56 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/LICENSE @@ -0,0 +1,26 @@ +Copyright 2013, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/local_packages/image_cropper-5.0.1/README.md b/local_packages/image_cropper-5.0.1/README.md new file mode 100644 index 0000000..0058c6f --- /dev/null +++ b/local_packages/image_cropper-5.0.1/README.md @@ -0,0 +1,275 @@ +# Image Cropper + +[![pub package](https://img.shields.io/pub/v/image_cropper.svg)](https://pub.dartlang.org/packages/image_cropper) + + +A Flutter plugin for Android, iOS and Web supports cropping images. This plugin is based on three different native libraries so it comes with different UI between these platforms. + +## Introduction + +**Image Cropper** doesn't manipulate images in Dart codes directly, instead, the plugin uses [Platform Channel](https://flutter.dev/docs/development/platform-integration/platform-channels) to expose Dart APIs that Flutter application can use to communicate with three very powerful native libraries ([uCrop](https://github.com/Yalantis/uCrop), [TOCropViewController](https://github.com/TimOliver/TOCropViewController) and [croppie](https://github.com/Foliotek/Croppie)) to crop and rotate images. Because of that, all credits belong to these libraries. + +### uCrop - Yalantis +[![GitHub watchers](https://img.shields.io/github/watchers/Yalantis/uCrop.svg?style=social&label=Watch&maxAge=2592000)](https://GitHub.com/Yalantis/uCrop/watchers/) [![GitHub stars](https://img.shields.io/github/stars/Yalantis/uCrop.svg?style=social&label=Star&maxAge=2592000)](https://GitHub.com/Yalantis/uCrop/stargazers/) [![GitHub forks](https://img.shields.io/github/forks/Yalantis/uCrop.svg?style=social&label=Fork&maxAge=2592000)](https://GitHub.com/Yalantis/uCrop/network/) [![](https://jitpack.io/v/Yalantis/uCrop.svg)](https://jitpack.io/#Yalantis/uCrop) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +This project aims to provide an ultimate and flexible image cropping experience. Made in [Yalantis](https://yalantis.com/?utm_source=github) + +

+ +

+ +### TOCropViewController - TimOliver +[![GitHub watchers](https://img.shields.io/github/watchers/TimOliver/TOCropViewController.svg?style=social&label=Watch&maxAge=2592000)](https://GitHub.com/TimOliver/TOCropViewController/watchers/) [![GitHub stars](https://img.shields.io/github/stars/TimOliver/TOCropViewController.svg?style=social&label=Star&maxAge=2592000)](https://GitHub.com/TimOliver/TOCropViewController/stargazers/) [![GitHub forks](https://img.shields.io/github/forks/TimOliver/TOCropViewController.svg?style=social&label=Fork&maxAge=2592000)](https://GitHub.com/TimOliver/TOCropViewController/network/) [![Version](https://img.shields.io/cocoapods/v/TOCropViewController.svg?style=flat)](http://cocoadocs.org/docsets/TOCropViewController) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/TimOliver/TOCropViewController/master/LICENSE) + +`TOCropViewController` is an open-source `UIViewController` subclass to crop out sections of `UIImage` objects, as well as perform basic rotations. It is excellent for things like editing profile pictures, or sharing parts of a photo online. It has been designed with the iOS Photos app editor in mind, and as such, behaves in a way that should already feel familiar to users of iOS. + +

+ +

+ +### Croppie - Foliotek +[![GitHub watchers](https://img.shields.io/github/watchers/foliotek/croppie.svg?style=social&label=Watch&maxAge=2592000)](https://GitHub.com/foliotek/croppie/watchers/) [![GitHub stars](https://img.shields.io/github/stars/foliotek/croppie.svg?style=social&label=Star&maxAge=2592000)](https://GitHub.com/foliotek/croppie/stargazers/) [![GitHub forks](https://img.shields.io/github/forks/foliotek/croppie.svg?style=social&label=Fork&maxAge=2592000)](https://GitHub.com/foliotek/croppie/network/) [![npm version](https://badge.fury.io/js/croppie.svg)](https://badge.fury.io/js/croppie) + +Croppie is a fast, easy to use image cropping plugin with tons of configuration options! + +

+ +

+ +## How to install + +### Android + +- Add UCropActivity into your AndroidManifest.xml + +````xml + +```` + +#### Note: +From v1.2.0, you need to migrate your android project to v2 embedding ([detail](https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects)) + +### iOS +- No configuration required + +### Web +- Add following codes inside `` tag in file `web/index.html`: + +```html + + .... + + + + + + + .... + +``` + +## Usage + +### Required parameters + +* **sourcePath**: the absolute path of an image file. + +### Optional parameters + +* **maxWidth**: maximum cropped image width. Note: this field is ignored on Web. + +* **maxHeight**: maximum cropped image height. Note: this field is ignored on Web. + +* **aspectRatio**: controls the aspect ratio of crop bounds. If this values is set, the cropper is locked and user can't change the aspect ratio of crop bounds. Note: this field is ignored on Web. + +* **aspectRatioPresets**: controls the list of aspect ratios in the crop menu view. In Android, you can set the initialized aspect ratio when starting the cropper by setting the value of `AndroidUiSettings.initAspectRatio`. Note: this field is ignored on Web. + +* **cropStyle**: controls the style of crop bounds, it can be rectangle or circle style (default is `CropStyle.rectangle`). Note: this field can be overrided by `WebUiSettings.viewPort.type` on Web + +* **compressFormat**: the format of result image, png or jpg (default is ImageCompressFormat.jpg). + +* **compressQuality**: number between 0 and 100 to control the quality of image compression. + +* **uiSettings**: controls UI customization on specific platform (android, ios, web,...) + + * Android: see [Android customization](#android-1). + + * iOS: see [iOS customization](#ios-1). + + * Web: see [Web customization](#web-1). + +### Note + +* The result file is saved in `NSTemporaryDirectory` on iOS and application Cache directory on Android, so it can be lost later, you are responsible for storing it somewhere permanent (if needed). + +* The implementation on Web is much different compared to the implementation on mobile app. It causes some configuration fields not working (`maxWidth`, `maxHeight`, `aspectRatio`, `aspectRatioPresets`) on Web. + +* `WebUiSettings` is required for Web. + +## Customization + +### Android + +
+Click to view detail +
+ +**Image Cropper** provides a helper class called `AndroidUiSettings` that wraps all properties can be used to customize UI in **uCrop** library. + +| Property | Description | Type | +|-----------------------------|-------------------------------------------------------------------------------------------------------------|-----------------------| +| `toolbarTitle` | desired text for Toolbar title | String | +| `toolbarColor` | desired color of the Toolbar | Color | +| `statusBarColor` | desired color of status | Color | +| `toolbarWidgetColor` | desired color of Toolbar text and buttons (default is darker orange) | Color | +| `backgroundColor` | desired background color that should be applied to the root view | Color | +| `activeControlsWidgetColor` | desired resolved color of the active and selected widget and progress wheel middle line (default is white) | Color | +| `dimmedLayerColor` | desired color of dimmed area around the crop bounds | Color | +| `cropFrameColor` | desired color of crop frame | Color | +| `cropGridColor` | desired color of crop grid/guidelines | Color | +| `cropFrameStrokeWidth` | desired width of crop frame line in pixels | int | +| `cropGridRowCount` | crop grid rows count | int | +| `cropGridColumnCount` | crop grid columns count | int | +| `cropGridStrokeWidth` | desired width of crop grid lines in pixels | int | +| `showCropGrid` | set to true if you want to see a crop grid/guidelines on top of an image | bool | +| `lockAspectRatio` | set to true if you want to lock the aspect ratio of crop bounds with a fixed value (locked by default) | bool | +| `hideBottomControls` | set to true to hide the bottom controls (shown by default) | bool | +| `initAspectRatio` | desired aspect ratio is applied (from the list of given aspect ratio presets) when starting the cropper | CropAspectRatioPreset | + +
+ +### iOS + +
+Click to view detail +
+ +**Image Cropper** provides a helper class called `IOUiSettings` that wraps all properties can be used to customize UI in **TOCropViewController** library. + +| Property | Description | Type | +|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------| +| `minimumAspectRatio` | The minimum croping aspect ratio. If set, user is prevented from setting cropping rectangle to lower aspect ratio than defined by the parameter | double | +| `rectX` | The initial rect of cropping: x. | double | +| `rectY` | The initial rect of cropping: y. | double | +| `rectWidth` | The initial rect of cropping: width. | double | +| `rectHeight` | The initial rect of cropping: height. | double | +| `showActivitySheetOnDone` | If true, when the user hits 'Done', a `UIActivityController` will appear before the view controller ends | bool | +| `showCancelConfirmationDialog` | Shows a confirmation dialog when the user hits 'Cancel' and there are pending changes (default is false) | bool | +| `rotateClockwiseButtonHidden` | When disabled, an additional rotation button that rotates the canvas in 90-degree segments in a clockwise direction is shown in the toolbar (default is false) | bool | +| `hidesNavigationBar` | If this controller is embedded in `UINavigationController` its navigation bar is hidden by default. Set this property to false to show the navigation bar. This must be set before this controller is presented | bool | +| `rotateButtonsHidden` | When enabled, hides the rotation button, as well as the alternative rotation button visible when `showClockwiseRotationButton` is set to YES (default is false) | bool | +| `resetButtonHidden` | When enabled, hides the 'Reset' button on the toolbar (default is false) | bool | +| `aspectRatioPickerButtonHidden` | When enabled, hides the 'Aspect Ratio Picker' button on the toolbar (default is false) | bool | +| `resetAspectRatioEnabled` | If true, tapping the reset button will also reset the aspect ratio back to the image default ratio. Otherwise, the reset will just zoom out to the current aspect ratio. If this is set to false, and `aspectRatioLockEnabled` is set to true, then the aspect ratio button will automatically be hidden from the toolbar (default is true) | bool | +| `aspectRatioLockDimensionSwapEnabled` | If true, a custom aspect ratio is set, and the `aspectRatioLockEnabled` is set to true, the crop box will swap it's dimensions depending on portrait or landscape sized images. This value also controls whether the dimensions can swap when the image is rotated (default is false) | bool | +| `aspectRatioLockEnabled` | If true, while it can still be resized, the crop box will be locked to its current aspect ratio. If this is set to true, and `resetAspectRatioEnabled` is set to false, then the aspect ratio button will automatically be hidden from the toolbar (default is false) | bool | +| `title` | Title text that appears at the top of the view controller. | String | +| `doneButtonTitle` | Title for the 'Done' button. Setting this will override the Default which is a localized string for "Done" | String | +| `cancelButtonTitle` | Title for the 'Cancel' button. Setting this will override the Default which is a localized string for "Cancel" | String | + +
+ +### Web + +
+Click to view detail +
+ +**Image Cropper** provides a helper class called `WebUiSettings` that wraps all properties can be used to customize UI in **croppie** library. + +| Property | Description | Type | +|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------| +| `boundary` | The outer container of the cropper. Default = { width: 500, height: 500 } | Boundary | +| `viewPort` | The inner container of the coppie. The visible part of the image. Valid type values:'square' 'circle'. Default = { width: 400, height: 400, type: 'square' } | ViewPort | +| `customClass` | A class of your choosing to add to the container to add custom styles to your croppie. Default = '' | String | +| `enableExif` | Enable exif orientation reading. Tells Croppie to read exif orientation from the image data and orient the image correctly before rendering to the page. Requires exif.js (packages/croppie_dart/lib/src/exif.js) | bool | +| `enableOrientation` | Enable or disable support for specifying a custom orientation when binding images. Default = true | bool | +| `enableZoom` | Enable zooming functionality. If set to false - scrolling and pinching would not zoom. Default = false | bool | +| `enableResize` | Enable or disable support for resizing the viewport area. Default = false | bool | +| `mouseWheelZoom` | Enable or disable the ability to use the mouse wheel to zoom in and out on a croppie instance. Default = true | bool | +| `showZoomer` | Hide or show the zoom slider. Default = true | bool | +| `presentStyle` | Presentation style of cropper, either a dialog or a page (route). Default = dialog | CropperPresentStyle | +| `context` | Current BuildContext. The context is required to show cropper dialog or route | BuildContext | +| `customDialogBuilder` | Builder to customize the cropper Dialog | CropperDialogBuilder | +| `customRouteBuilder` | Builder to customize the cropper PageRoute | CropperRouteBuilder | + +#### Note: + +If using `CropperDialogBuilder` and `CropperRouteBuilder` to customize cropper dialog and route, the customization codes need to call `crop()` function to trigger crop feature and then returning the cropped result data to the plugin by using `Navigator.of(context).pop(result)`. + +````dart + + WebUiSettings( + ... + customDialogBuilder: (cropper, crop, rotate) { + return Dialog( + child: Builder( + builder: (context) { + return Column( + children: [ + ... + cropper, + ... + TextButton( + onPressed: () async { + /// it is important to call crop() function and return + /// result data to plugin, for example: + final result = await crop(); + Navigator.of(context).pop(result); + }, + child: Text('Crop'), + ) + ] + ); + }, + ), + ); + }, + ... + ) + +```` + +
+ +## Example + +````dart + +import 'package:image_cropper/image_cropper.dart'; + +CroppedFile croppedFile = await ImageCropper().cropImage( + sourcePath: imageFile.path, + aspectRatioPresets: [ + CropAspectRatioPreset.square, + CropAspectRatioPreset.ratio3x2, + CropAspectRatioPreset.original, + CropAspectRatioPreset.ratio4x3, + CropAspectRatioPreset.ratio16x9 + ], + uiSettings: [ + AndroidUiSettings( + toolbarTitle: 'Cropper', + toolbarColor: Colors.deepOrange, + toolbarWidgetColor: Colors.white, + initAspectRatio: CropAspectRatioPreset.original, + lockAspectRatio: false), + IOSUiSettings( + title: 'Cropper', + ), + WebUiSettings( + context: context, + ), + ], + ); + +```` + +## Credits + +- Android: [uCrop](https://github.com/Yalantis/uCrop) created by [Yalantis](https://github.com/Yalantis) +- iOS: [TOCropViewController](https://github.com/TimOliver/TOCropViewController) created by [Tim Oliver](https://twitter.com/TimOliverAU) +- Web: [croppie](https://github.com/Foliotek/Croppie) created by [Foliotek](https://github.com/Foliotek) and [croppie-dart](https://gitlab.com/michel.werren/croppie-dart) created by [Michel Werren](https://gitlab.com/michel.werren) diff --git a/local_packages/image_cropper-5.0.1/android/build.gradle b/local_packages/image_cropper-5.0.1/android/build.gradle new file mode 100644 index 0000000..af83905 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/android/build.gradle @@ -0,0 +1,43 @@ +group 'vn.hunghd.flutter.plugins.imagecropper' +version '1.0-SNAPSHOT' + +buildscript { + repositories { + google() + mavenCentral() + maven { url "https://www.jitpack.io" } + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.11.1' + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + maven { url "https://www.jitpack.io" } + } +} + +apply plugin: 'com.android.library' + +android { + // Conditional for compatibility with AGP <4.2. + if (project.android.hasProperty("namespace")) { + namespace 'vn.hunghd.flutter.plugins.imagecropper' + } + compileSdkVersion 31 + + defaultConfig { + minSdkVersion 16 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } + dependencies { + implementation 'com.github.yalantis:ucrop:2.2.8' + } +} diff --git a/local_packages/image_cropper-5.0.1/android/gradle.properties b/local_packages/image_cropper-5.0.1/android/gradle.properties new file mode 100644 index 0000000..53ae0ae --- /dev/null +++ b/local_packages/image_cropper-5.0.1/android/gradle.properties @@ -0,0 +1,3 @@ +android.enableJetifier=true +android.useAndroidX=true +org.gradle.jvmargs=-Xmx1536M diff --git a/local_packages/image_cropper-5.0.1/android/gradle/wrapper/gradle-wrapper.jar b/local_packages/image_cropper-5.0.1/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/local_packages/image_cropper-5.0.1/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/local_packages/image_cropper-5.0.1/android/gradle/wrapper/gradle-wrapper.properties b/local_packages/image_cropper-5.0.1/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e42f9a2 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Mar 29 16:49:03 EET 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/local_packages/image_cropper-5.0.1/android/gradlew b/local_packages/image_cropper-5.0.1/android/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/local_packages/image_cropper-5.0.1/android/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/local_packages/image_cropper-5.0.1/android/settings.gradle b/local_packages/image_cropper-5.0.1/android/settings.gradle new file mode 100644 index 0000000..8228025 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'image_cropper' diff --git a/local_packages/image_cropper-5.0.1/android/src/main/AndroidManifest.xml b/local_packages/image_cropper-5.0.1/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f6e0a3f --- /dev/null +++ b/local_packages/image_cropper-5.0.1/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/local_packages/image_cropper-5.0.1/android/src/main/java/vn/hunghd/flutter/plugins/imagecropper/FileUtils.java b/local_packages/image_cropper-5.0.1/android/src/main/java/vn/hunghd/flutter/plugins/imagecropper/FileUtils.java new file mode 100644 index 0000000..641db14 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/android/src/main/java/vn/hunghd/flutter/plugins/imagecropper/FileUtils.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2007-2008 OpenIntents.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file was modified by the Flutter authors from the following original file: + * https://raw.githubusercontent.com/iPaulPro/aFileChooser/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java + */ + +package vn.hunghd.flutter.plugins.imagecropper; + +import android.annotation.SuppressLint; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.text.TextUtils; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +class FileUtils { + + String getPathFromUri(final Context context, final Uri uri) { + String path = getPathFromLocalUri(context, uri); + if (path == null) { + path = getPathFromRemoteUri(context, uri); + } + return path; + } + + @SuppressLint("NewApi") + private String getPathFromLocalUri(final Context context, final Uri uri) { + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + } else if (isDownloadsDocument(uri)) { + final String id = DocumentsContract.getDocumentId(uri); + + if (!TextUtils.isEmpty(id)) { + try { + final Uri contentUri = + ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + return getDataColumn(context, contentUri, null, null); + } catch (NumberFormatException e) { + return null; + } + } + + } else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[] {split[1]}; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } else if ("content".equalsIgnoreCase(uri.getScheme())) { + + // Return the remote address + if (isGooglePhotosUri(uri)) { + return uri.getLastPathSegment(); + } + + return getDataColumn(context, uri, null, null); + } else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + private static String getDataColumn( + Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + + final String column = "_data"; + final String[] projection = {column}; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + private static String getPathFromRemoteUri(final Context context, final Uri uri) { + // The code below is why Java now has try-with-resources and the Files utility. + File file = null; + InputStream inputStream = null; + OutputStream outputStream = null; + boolean success = false; + try { + inputStream = context.getContentResolver().openInputStream(uri); + file = File.createTempFile("image_picker", "jpg", context.getCacheDir()); + outputStream = new FileOutputStream(file); + if (inputStream != null) { + copy(inputStream, outputStream); + success = true; + } + } catch (IOException ignored) { + } finally { + try { + if (inputStream != null) inputStream.close(); + } catch (IOException ignored) { + } + try { + if (outputStream != null) outputStream.close(); + } catch (IOException ignored) { + // If closing the output stream fails, we cannot be sure that the + // target file was written in full. Flushing the stream merely moves + // the bytes into the OS, not necessarily to the file. + success = false; + } + } + return success ? file.getPath() : null; + } + + private static void copy(InputStream in, OutputStream out) throws IOException { + final byte[] buffer = new byte[4 * 1024]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + out.flush(); + } + + private static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + private static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + private static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + private static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } +} diff --git a/local_packages/image_cropper-5.0.1/android/src/main/java/vn/hunghd/flutter/plugins/imagecropper/ImageCropperDelegate.java b/local_packages/image_cropper-5.0.1/android/src/main/java/vn/hunghd/flutter/plugins/imagecropper/ImageCropperDelegate.java new file mode 100644 index 0000000..0377bbd --- /dev/null +++ b/local_packages/image_cropper-5.0.1/android/src/main/java/vn/hunghd/flutter/plugins/imagecropper/ImageCropperDelegate.java @@ -0,0 +1,266 @@ +package vn.hunghd.flutter.plugins.imagecropper; + +import android.app.Activity; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.net.Uri; +import android.preference.PreferenceManager; + +import com.yalantis.ucrop.UCrop; +import com.yalantis.ucrop.model.AspectRatio; +import com.yalantis.ucrop.view.CropImageView; + +import java.io.File; +import java.util.ArrayList; +import java.util.Date; + +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.PluginRegistry; + +import static android.app.Activity.RESULT_OK; + +public class ImageCropperDelegate implements PluginRegistry.ActivityResultListener { + static final String FILENAME_CACHE_KEY = "imagecropper.FILENAME_CACHE_KEY"; + + private final Activity activity; + private final SharedPreferences preferences; + private MethodChannel.Result pendingResult; + private FileUtils fileUtils; + + public ImageCropperDelegate(Activity activity) { + this.activity = activity; + preferences = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); + fileUtils = new FileUtils(); + } + + public void startCrop(MethodCall call, MethodChannel.Result result) { + String sourcePath = call.argument("source_path"); + Integer maxWidth = call.argument("max_width"); + Integer maxHeight = call.argument("max_height"); + Double ratioX = call.argument("ratio_x"); + Double ratioY = call.argument("ratio_y"); + String cropStyle = call.argument("crop_style"); + String compressFormat = call.argument("compress_format"); + Integer compressQuality = call.argument("compress_quality"); + ArrayList aspectRatioPresets = call.argument("aspect_ratio_presets"); + String initAspectRatio = call.argument("android.init_aspect_ratio"); + + pendingResult = result; + + File outputDir = activity.getCacheDir(); + File outputFile; + if("png".equals(compressFormat)){ + outputFile = new File(outputDir, "image_cropper_" + (new Date()).getTime() + ".png"); + } else { + outputFile = new File(outputDir, "image_cropper_" + (new Date()).getTime() + ".jpg"); + } + Uri sourceUri = Uri.fromFile(new File(sourcePath)); + Uri destinationUri = Uri.fromFile(outputFile); + + UCrop.Options options = new UCrop.Options(); + // uCrop.withMaxResultSize(1000, 1000); + options.setCompressionFormat("png".equals(compressFormat) ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG); + options.setCompressionQuality(compressQuality != null ? compressQuality : 90); + options.setMaxBitmapSize(10000); + + // UI customization settings + if ("circle".equals(cropStyle)) { + options.setCircleDimmedLayer(true); + } + setupUiCustomizedOptions(options, call); + + if (aspectRatioPresets != null) { + ArrayList aspectRatioList = new ArrayList<>(); + int defaultIndex = 0; + for (int i = 0; i < aspectRatioPresets.size(); i++) { + String preset = aspectRatioPresets.get(i); + if (preset != null) { + AspectRatio aspectRatio = parseAspectRatioName(preset); + aspectRatioList.add(aspectRatio); + if (preset.equals(initAspectRatio)) { + defaultIndex = i; + } + } + } + options.setAspectRatioOptions(defaultIndex, aspectRatioList.toArray(new AspectRatio[]{})); + } + + UCrop cropper = UCrop.of(sourceUri, destinationUri).withOptions(options); + if (maxWidth != null && maxHeight != null) { + cropper.withMaxResultSize(maxWidth, maxHeight); + } + if (ratioX != null && ratioY != null) { + cropper.withAspectRatio(ratioX.floatValue(), ratioY.floatValue()); + } + + activity.startActivityForResult(cropper.getIntent(activity), UCrop.REQUEST_CROP); + } + + public void recoverImage(MethodCall call, MethodChannel.Result result) { + result.success(getAndClearCachedImage()); + } + + private void cacheImage(String filePath) { + SharedPreferences.Editor editor = preferences.edit(); + editor.putString(FILENAME_CACHE_KEY, filePath); + editor.apply(); + } + + private String getAndClearCachedImage() { + if (preferences.contains(FILENAME_CACHE_KEY)) { + String result = preferences.getString(FILENAME_CACHE_KEY, ""); + SharedPreferences.Editor editor = preferences.edit(); + editor.remove(FILENAME_CACHE_KEY); + editor.apply(); + return result; + } + return null; + } + + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == UCrop.REQUEST_CROP) { + if (resultCode == RESULT_OK) { + final Uri resultUri = UCrop.getOutput(data); + final String imagePath = fileUtils.getPathFromUri(activity, resultUri); + cacheImage(imagePath); + finishWithSuccess(imagePath); + return true; + } else if (resultCode == UCrop.RESULT_ERROR) { + final Throwable cropError = UCrop.getError(data); + finishWithError("crop_error", cropError.getLocalizedMessage(), cropError); + return true; + } else if (pendingResult != null) { + pendingResult.success(null); + clearMethodCallAndResult(); + return true; + } + } + return false; + } + + private void finishWithSuccess(String imagePath) { + if (pendingResult != null) { + pendingResult.success(imagePath); + clearMethodCallAndResult(); + } + } + + private void finishWithError(String errorCode, String errorMessage, Throwable throwable) { + if (pendingResult != null) { + pendingResult.error(errorCode, errorMessage, throwable); + clearMethodCallAndResult(); + } + } + + private UCrop.Options setupUiCustomizedOptions(UCrop.Options options, MethodCall call) { + String title = call.argument("android.toolbar_title"); + Integer toolbarColor = call.argument("android.toolbar_color"); + Integer statusBarColor = call.argument("android.statusbar_color"); + Integer toolbarWidgetColor = call.argument("android.toolbar_widget_color"); + Integer backgroundColor = call.argument("android.background_color"); + Integer activeControlsWidgetColor = call.argument("android.active_controls_widget_color"); + Integer dimmedLayerColor = call.argument("android.dimmed_layer_color"); + Integer cropFrameColor = call.argument("android.crop_frame_color"); + Integer cropGridColor = call.argument("android.crop_grid_color"); + Integer cropFrameStrokeWidth = call.argument("android.crop_frame_stroke_width"); + Integer cropGridRowCount = call.argument("android.crop_grid_row_count"); + Integer cropGridColumnCount = call.argument("android.crop_grid_column_count"); + Integer cropGridStrokeWidth = call.argument("android.crop_grid_stroke_width"); + Boolean showCropGrid = call.argument("android.show_crop_grid"); + Boolean lockAspectRatio = call.argument("android.lock_aspect_ratio"); + Boolean hideBottomControls = call.argument("android.hide_bottom_controls"); + + if (title != null) { + options.setToolbarTitle(title); + } + if (toolbarColor != null) { + options.setToolbarColor(toolbarColor); + } + if (statusBarColor != null) { + options.setStatusBarColor(statusBarColor); + } else if (toolbarColor != null) { + options.setStatusBarColor(darkenColor(toolbarColor)); + } + if (toolbarWidgetColor != null) { + options.setToolbarWidgetColor(toolbarWidgetColor); + } + if (backgroundColor != null) { + options.setRootViewBackgroundColor(backgroundColor); + } + if (activeControlsWidgetColor != null) { + options.setActiveControlsWidgetColor(activeControlsWidgetColor); + } + if (dimmedLayerColor != null) { + options.setDimmedLayerColor(dimmedLayerColor); + } + if (cropFrameColor != null) { + options.setCropFrameColor(cropFrameColor); + } + if (cropGridColor != null) { + options.setCropGridColor(cropGridColor); + } + if (cropFrameStrokeWidth != null) { + options.setCropFrameStrokeWidth(cropFrameStrokeWidth); + } + if (cropGridRowCount != null) { + options.setCropGridRowCount(cropGridRowCount); + } + if (cropGridColumnCount != null) { + options.setCropGridColumnCount(cropGridColumnCount); + } + if (cropGridStrokeWidth != null) { + options.setCropGridStrokeWidth(cropGridStrokeWidth); + } + if (showCropGrid != null) { + options.setShowCropGrid(showCropGrid); + } + if (lockAspectRatio != null) { + options.setFreeStyleCropEnabled(!lockAspectRatio); + } + if (hideBottomControls != null) { + options.setHideBottomControls(hideBottomControls); + } + + return options; + } + + + private void clearMethodCallAndResult() { + pendingResult = null; + } + + private int darkenColor(int color) { + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + hsv[2] *= 0.8f; + return Color.HSVToColor(hsv); + } + + private AspectRatio parseAspectRatioName(String name) { + if ("square".equals(name)) { + return new AspectRatio(null, 1.0f, 1.0f); + } else if ("original".equals(name)) { + return new AspectRatio(activity.getString(com.yalantis.ucrop.R.string.ucrop_label_original).toUpperCase(), + CropImageView.SOURCE_IMAGE_ASPECT_RATIO, 1.0f); + } else if ("3x2".equals(name)) { + return new AspectRatio(null, 3.0f, 2.0f); + } else if ("4x3".equals(name)) { + return new AspectRatio(null, 4.0f, 3.0f); + } else if ("5x3".equals(name)) { + return new AspectRatio(null, 5.0f, 3.0f); + } else if ("5x4".equals(name)) { + return new AspectRatio(null, 5.0f, 4.0f); + } else if ("7x5".equals(name)) { + return new AspectRatio(null, 7.0f, 5.0f); + } else if ("16x9".equals(name)) { + return new AspectRatio(null, 16.0f, 9.0f); + } else { + return new AspectRatio(activity.getString(com.yalantis.ucrop.R.string.ucrop_label_original).toUpperCase(), + CropImageView.SOURCE_IMAGE_ASPECT_RATIO, 1.0f); + } + } +} diff --git a/local_packages/image_cropper-5.0.1/android/src/main/java/vn/hunghd/flutter/plugins/imagecropper/ImageCropperPlugin.java b/local_packages/image_cropper-5.0.1/android/src/main/java/vn/hunghd/flutter/plugins/imagecropper/ImageCropperPlugin.java new file mode 100644 index 0000000..6982e6b --- /dev/null +++ b/local_packages/image_cropper-5.0.1/android/src/main/java/vn/hunghd/flutter/plugins/imagecropper/ImageCropperPlugin.java @@ -0,0 +1,90 @@ +package vn.hunghd.flutter.plugins.imagecropper; + + +import android.app.Activity; + +import androidx.appcompat.app.AppCompatDelegate; + +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; + +/** + * ImageCropperPlugin + */ +public class ImageCropperPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware { + static { + AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); + } + + private static final String CHANNEL = "plugins.hunghd.vn/image_cropper"; + private ImageCropperDelegate delegate; + + private ActivityPluginBinding activityPluginBinding; + + + private void setupEngine(BinaryMessenger messenger) { + MethodChannel channel = new MethodChannel(messenger, CHANNEL); + channel.setMethodCallHandler(this); + } + + public ImageCropperDelegate setupActivity(Activity activity) { + delegate = new ImageCropperDelegate(activity); + return delegate; + } + + @Override + public void onMethodCall(MethodCall call, Result result) { + + if (call.method.equals("cropImage")) { + delegate.startCrop(call, result); + } else if (call.method.equals("recoverImage")) { + delegate.recoverImage(call, result); + } + + } + ////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) { + setupEngine(flutterPluginBinding.getBinaryMessenger()); + } + + @Override + public void onAttachedToActivity(ActivityPluginBinding activityPluginBinding) { + + setupActivity(activityPluginBinding.getActivity()); + this.activityPluginBinding = activityPluginBinding; + activityPluginBinding.addActivityResultListener(delegate); + } + ////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onDetachedFromEngine(FlutterPluginBinding flutterPluginBinding) { + // no need to clear channel + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity(); + } + + @Override + public void onDetachedFromActivity() { + activityPluginBinding.removeActivityResultListener(delegate); + activityPluginBinding = null; + delegate = null; + } + + @Override + public void onReattachedToActivityForConfigChanges(ActivityPluginBinding activityPluginBinding) { + onAttachedToActivity(activityPluginBinding); + } + + +} diff --git a/local_packages/image_cropper-5.0.1/example/README.md b/local_packages/image_cropper-5.0.1/example/README.md new file mode 100644 index 0000000..c9ff5b8 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/README.md @@ -0,0 +1,8 @@ +# image_cropper_example + +Demonstrates how to use the image_cropper plugin. + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](https://flutter.io/). diff --git a/local_packages/image_cropper-5.0.1/example/analysis_options.yaml b/local_packages/image_cropper-5.0.1/example/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/local_packages/image_cropper-5.0.1/example/android/app/build.gradle b/local_packages/image_cropper-5.0.1/example/android/app/build.gradle new file mode 100644 index 0000000..2433eb5 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/android/app/build.gradle @@ -0,0 +1,69 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + namespace 'com.example.image_cropper_example' + compileSdkVersion flutter.compileSdkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.image_cropper_example" + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/local_packages/image_cropper-5.0.1/example/android/app/src/debug/AndroidManifest.xml b/local_packages/image_cropper-5.0.1/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..f880684 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + diff --git a/local_packages/image_cropper-5.0.1/example/android/app/src/main/AndroidManifest.xml b/local_packages/image_cropper-5.0.1/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1e87095 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + diff --git a/local_packages/image_cropper-5.0.1/example/android/app/src/main/kotlin/com/example/image_cropper_example/MainActivity.kt b/local_packages/image_cropper-5.0.1/example/android/app/src/main/kotlin/com/example/image_cropper_example/MainActivity.kt new file mode 100644 index 0000000..ab6dc27 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/android/app/src/main/kotlin/com/example/image_cropper_example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.image_cropper_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/drawable-v21/launch_background.xml b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/drawable/launch_background.xml b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/values-night/styles.xml b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..3db14bb --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/values/styles.xml b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..d460d1e --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/image_cropper-5.0.1/example/android/app/src/profile/AndroidManifest.xml b/local_packages/image_cropper-5.0.1/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..f880684 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + diff --git a/local_packages/image_cropper-5.0.1/example/android/build.gradle b/local_packages/image_cropper-5.0.1/example/android/build.gradle new file mode 100644 index 0000000..f337ed7 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.9.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.0.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/local_packages/image_cropper-5.0.1/example/android/gradle.properties b/local_packages/image_cropper-5.0.1/example/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/local_packages/image_cropper-5.0.1/example/android/gradle/wrapper/gradle-wrapper.properties b/local_packages/image_cropper-5.0.1/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bf4b712 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun May 14 15:37:24 ICT 2023 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/local_packages/image_cropper-5.0.1/example/android/settings.gradle b/local_packages/image_cropper-5.0.1/example/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/local_packages/image_cropper-5.0.1/example/ios/Flutter/AppFrameworkInfo.plist b/local_packages/image_cropper-5.0.1/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/local_packages/image_cropper-5.0.1/example/ios/Flutter/Debug.xcconfig b/local_packages/image_cropper-5.0.1/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/image_cropper-5.0.1/example/ios/Flutter/Release.xcconfig b/local_packages/image_cropper-5.0.1/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/image_cropper-5.0.1/example/ios/Podfile b/local_packages/image_cropper-5.0.1/example/ios/Podfile new file mode 100644 index 0000000..88359b2 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/local_packages/image_cropper-5.0.1/example/ios/Podfile.lock b/local_packages/image_cropper-5.0.1/example/ios/Podfile.lock new file mode 100644 index 0000000..2f4f515 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Podfile.lock @@ -0,0 +1,35 @@ +PODS: + - Flutter (1.0.0) + - image_cropper (0.0.4): + - Flutter + - TOCropViewController (~> 2.6.1) + - image_picker_ios (0.0.1): + - Flutter + - TOCropViewController (2.6.1) + +DEPENDENCIES: + - Flutter (from `Flutter`) + - image_cropper (from `.symlinks/plugins/image_cropper/ios`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + +SPEC REPOS: + trunk: + - TOCropViewController + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + image_cropper: + :path: ".symlinks/plugins/image_cropper/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" + +SPEC CHECKSUMS: + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + image_cropper: a3291c624a953049bc6a02e1f8c8ceb162a24b25 + image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 + TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 + +PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 + +COCOAPODS: 1.12.1 diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/project.pbxproj b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..49d213c --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,555 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 746ABECA02FA57E9E70285F8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F1EC9FEC7959986DBF1AB9F6 /* Pods_Runner.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3EB67857DEC06F6F9C6A6E22 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 4D01CE2E8E0BA716FF73F657 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9B98007263B5CFE983E60B4D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + F1EC9FEC7959986DBF1AB9F6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 746ABECA02FA57E9E70285F8 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 91F8C477FE2793BAB45A226B /* Pods */ = { + isa = PBXGroup; + children = ( + 3EB67857DEC06F6F9C6A6E22 /* Pods-Runner.debug.xcconfig */, + 4D01CE2E8E0BA716FF73F657 /* Pods-Runner.release.xcconfig */, + 9B98007263B5CFE983E60B4D /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 91F8C477FE2793BAB45A226B /* Pods */, + CBAF564AB8D53B537C21DD90 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + CBAF564AB8D53B537C21DD90 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F1EC9FEC7959986DBF1AB9F6 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 0DB42D733D265E7EF900C219 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + C591FDB819DC138A97971C2E /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0DB42D733D265E7EF900C219 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + C591FDB819DC138A97971C2E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 6ZWDSQ44A2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.imageCropperExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 6ZWDSQ44A2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.imageCropperExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 6ZWDSQ44A2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.imageCropperExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a6b826d --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/AppDelegate.swift b/local_packages/image_cropper-5.0.1/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/local_packages/image_cropper-5.0.1/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Base.lproj/Main.storyboard b/local_packages/image_cropper-5.0.1/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Info.plist b/local_packages/image_cropper-5.0.1/example/ios/Runner/Info.plist new file mode 100644 index 0000000..88d296a --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner/Info.plist @@ -0,0 +1,55 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Image Cropper Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + image_cropper_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NSCameraUsageDescription + I need to access your Camera + NSPhotoLibraryUsageDescription + I need to access your Photo Library + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/local_packages/image_cropper-5.0.1/example/ios/Runner/Runner-Bridging-Header.h b/local_packages/image_cropper-5.0.1/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/local_packages/image_cropper-5.0.1/example/lib/generated_plugin_registrant.dart b/local_packages/image_cropper-5.0.1/example/lib/generated_plugin_registrant.dart new file mode 100644 index 0000000..8ce7b6f --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/lib/generated_plugin_registrant.dart @@ -0,0 +1,19 @@ +// +// Generated file. Do not edit. +// + +// ignore_for_file: directives_ordering +// ignore_for_file: lines_longer_than_80_chars +// ignore_for_file: depend_on_referenced_packages + +import 'package:image_cropper_for_web/image_cropper_for_web.dart'; +import 'package:image_picker_for_web/image_picker_for_web.dart'; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +// ignore: public_member_api_docs +void registerPlugins(Registrar registrar) { + ImageCropperPlugin.registerWith(registrar); + ImagePickerPlugin.registerWith(registrar); + registrar.registerMessageHandler(); +} diff --git a/local_packages/image_cropper-5.0.1/example/lib/main.dart b/local_packages/image_cropper-5.0.1/example/lib/main.dart new file mode 100644 index 0000000..d1536e0 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/lib/main.dart @@ -0,0 +1,314 @@ +import 'dart:io'; + +import 'package:dotted_border/dotted_border.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:image_cropper/image_cropper.dart'; +import 'package:image_picker/image_picker.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + highlightColor: const Color(0xFFD0996F), + canvasColor: const Color(0xFFFDF5EC), + textTheme: TextTheme( + headlineSmall: ThemeData.light() + .textTheme + .headlineSmall! + .copyWith(color: const Color(0xFFBC764A)), + ), + iconTheme: IconThemeData( + color: Colors.grey[600], + ), + appBarTheme: const AppBarTheme( + backgroundColor: Color(0xFFBC764A), + centerTitle: false, + foregroundColor: Colors.white, + actionsIconTheme: IconThemeData(color: Colors.white), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ButtonStyle( + backgroundColor: MaterialStateColor.resolveWith( + (states) => const Color(0xFFBC764A)), + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: ButtonStyle( + foregroundColor: MaterialStateColor.resolveWith( + (states) => const Color(0xFFBC764A), + ), + side: MaterialStateBorderSide.resolveWith( + (states) => const BorderSide(color: Color(0xFFBC764A))), + ), + ), + colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blue) + .copyWith(background: const Color(0xFFFDF5EC))), + home: const HomePage(title: 'Image Cropper Demo'), + ); + } +} + +class HomePage extends StatefulWidget { + final String title; + + const HomePage({ + Key? key, + required this.title, + }) : super(key: key); + + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + XFile? _pickedFile; + CroppedFile? _croppedFile; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: !kIsWeb ? AppBar(title: Text(widget.title)) : null, + body: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (kIsWeb) + Padding( + padding: const EdgeInsets.all(kIsWeb ? 24.0 : 16.0), + child: Text( + widget.title, + style: Theme.of(context) + .textTheme + .displayMedium! + .copyWith(color: Theme.of(context).highlightColor), + ), + ), + Expanded(child: _body()), + ], + ), + ); + } + + Widget _body() { + if (_croppedFile != null || _pickedFile != null) { + return _imageCard(); + } else { + return _uploaderCard(); + } + } + + Widget _imageCard() { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: kIsWeb ? 24.0 : 16.0), + child: Card( + elevation: 4.0, + child: Padding( + padding: const EdgeInsets.all(kIsWeb ? 24.0 : 16.0), + child: _image(), + ), + ), + ), + const SizedBox(height: 24.0), + _menu(), + ], + ), + ); + } + + Widget _image() { + final screenWidth = MediaQuery.of(context).size.width; + final screenHeight = MediaQuery.of(context).size.height; + if (_croppedFile != null) { + final path = _croppedFile!.path; + return ConstrainedBox( + constraints: BoxConstraints( + maxWidth: 0.8 * screenWidth, + maxHeight: 0.7 * screenHeight, + ), + child: kIsWeb ? Image.network(path) : Image.file(File(path)), + ); + } else if (_pickedFile != null) { + final path = _pickedFile!.path; + return ConstrainedBox( + constraints: BoxConstraints( + maxWidth: 0.8 * screenWidth, + maxHeight: 0.7 * screenHeight, + ), + child: kIsWeb ? Image.network(path) : Image.file(File(path)), + ); + } else { + return const SizedBox.shrink(); + } + } + + Widget _menu() { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + FloatingActionButton( + onPressed: () { + _clear(); + }, + backgroundColor: Colors.redAccent, + tooltip: 'Delete', + child: const Icon(Icons.delete), + ), + if (_croppedFile == null) + Padding( + padding: const EdgeInsets.only(left: 32.0), + child: FloatingActionButton( + onPressed: () { + _cropImage(); + }, + backgroundColor: const Color(0xFFBC764A), + tooltip: 'Crop', + child: const Icon(Icons.crop), + ), + ) + ], + ); + } + + Widget _uploaderCard() { + return Center( + child: Card( + elevation: 4.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.0), + ), + child: SizedBox( + width: kIsWeb ? 380.0 : 320.0, + height: 300.0, + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: DottedBorder( + radius: const Radius.circular(12.0), + borderType: BorderType.RRect, + dashPattern: const [8, 4], + color: Theme.of(context).highlightColor.withOpacity(0.4), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.image, + color: Theme.of(context).highlightColor, + size: 80.0, + ), + const SizedBox(height: 24.0), + Text( + 'Upload an image to start', + style: kIsWeb + ? Theme.of(context) + .textTheme + .headlineSmall! + .copyWith( + color: Theme.of(context).highlightColor) + : Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + Theme.of(context).highlightColor), + ) + ], + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 24.0), + child: ElevatedButton( + onPressed: () { + _uploadImage(); + }, + child: const Text('Upload'), + ), + ), + ], + ), + ), + ), + ); + } + + Future _cropImage() async { + if (_pickedFile != null) { + final croppedFile = await ImageCropper().cropImage( + sourcePath: _pickedFile!.path, + compressFormat: ImageCompressFormat.jpg, + compressQuality: 100, + uiSettings: [ + AndroidUiSettings( + toolbarTitle: 'Cropper', + toolbarColor: Colors.deepOrange, + toolbarWidgetColor: Colors.white, + initAspectRatio: CropAspectRatioPreset.original, + lockAspectRatio: false), + IOSUiSettings( + title: 'Cropper', + ), + WebUiSettings( + context: context, + presentStyle: CropperPresentStyle.dialog, + boundary: const CroppieBoundary( + width: 520, + height: 520, + ), + viewPort: + const CroppieViewPort(width: 480, height: 480, type: 'circle'), + enableExif: true, + enableZoom: true, + showZoomer: true, + ), + ], + ); + if (croppedFile != null) { + setState(() { + _croppedFile = croppedFile; + }); + } + } + } + + Future _uploadImage() async { + final pickedFile = + await ImagePicker().pickImage(source: ImageSource.gallery); + if (pickedFile != null) { + setState(() { + _pickedFile = pickedFile; + }); + } + } + + void _clear() { + setState(() { + _pickedFile = null; + _croppedFile = null; + }); + } +} diff --git a/local_packages/image_cropper-5.0.1/example/pubspec.yaml b/local_packages/image_cropper-5.0.1/example/pubspec.yaml new file mode 100644 index 0000000..97bfb08 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/pubspec.yaml @@ -0,0 +1,40 @@ +name: image_cropper_example +description: Demonstrates how to use the flutter_image_cropper plugin. + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# Read more about versioning at semver.org. +version: 1.0.0+1 + +environment: + sdk: ">=2.16.2 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + image_picker: ^0.8.7+5 + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.4 + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_lints: ^1.0.0 + + dotted_border: ^2.0.0+2 + image_cropper: + path: ../ + +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true diff --git a/local_packages/image_cropper-5.0.1/example/web/favicon.png b/local_packages/image_cropper-5.0.1/example/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/web/favicon.png differ diff --git a/local_packages/image_cropper-5.0.1/example/web/icons/Icon-192.png b/local_packages/image_cropper-5.0.1/example/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/web/icons/Icon-192.png differ diff --git a/local_packages/image_cropper-5.0.1/example/web/icons/Icon-512.png b/local_packages/image_cropper-5.0.1/example/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/web/icons/Icon-512.png differ diff --git a/local_packages/image_cropper-5.0.1/example/web/icons/Icon-maskable-192.png b/local_packages/image_cropper-5.0.1/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/web/icons/Icon-maskable-192.png differ diff --git a/local_packages/image_cropper-5.0.1/example/web/icons/Icon-maskable-512.png b/local_packages/image_cropper-5.0.1/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/example/web/icons/Icon-maskable-512.png differ diff --git a/local_packages/image_cropper-5.0.1/example/web/index.html b/local_packages/image_cropper-5.0.1/example/web/index.html new file mode 100644 index 0000000..aa2f08b --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/web/index.html @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + image_cropper_example + + + + + + + + + + + + + diff --git a/local_packages/image_cropper-5.0.1/example/web/manifest.json b/local_packages/image_cropper-5.0.1/example/web/manifest.json new file mode 100644 index 0000000..1b65a7c --- /dev/null +++ b/local_packages/image_cropper-5.0.1/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "image_cropper_example", + "short_name": "image_cropper_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/local_packages/image_cropper-5.0.1/ios/Classes/FLTImageCropperPlugin.h b/local_packages/image_cropper-5.0.1/ios/Classes/FLTImageCropperPlugin.h new file mode 100644 index 0000000..0cee319 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/ios/Classes/FLTImageCropperPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface FLTImageCropperPlugin : NSObject +@end diff --git a/local_packages/image_cropper-5.0.1/ios/Classes/FLTImageCropperPlugin.m b/local_packages/image_cropper-5.0.1/ios/Classes/FLTImageCropperPlugin.m new file mode 100644 index 0000000..fc6b4fa --- /dev/null +++ b/local_packages/image_cropper-5.0.1/ios/Classes/FLTImageCropperPlugin.m @@ -0,0 +1,316 @@ +#import "FLTImageCropperPlugin.h" +#import +#import +#import +#import + +@interface FLTImageCropperPlugin() +@end + +@implementation FLTImageCropperPlugin { + FlutterResult _result; + NSDictionary *_arguments; + UIViewController *_viewController; + float _compressQuality; + NSString *_compressFormat; +} ++ (void)registerWithRegistrar:(NSObject*)registrar { + FlutterMethodChannel* channel = [FlutterMethodChannel + methodChannelWithName:@"plugins.hunghd.vn/image_cropper" + binaryMessenger:[registrar messenger]]; + UIViewController *viewController = [UIApplication sharedApplication].delegate.window.rootViewController; + FLTImageCropperPlugin* instance = [[FLTImageCropperPlugin alloc] initWithViewController:viewController]; + [registrar addMethodCallDelegate:instance channel:channel]; +} + +- (instancetype)initWithViewController:(UIViewController *)viewController { + self = [super init]; + if (self) { + _viewController = viewController; + } + return self; +} + +- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { + if ([@"cropImage" isEqualToString:call.method]) { + _result = result; + _arguments = call.arguments; + NSString *sourcePath = call.arguments[@"source_path"]; + NSNumber *ratioX = call.arguments[@"ratio_x"]; + NSNumber *ratioY = call.arguments[@"ratio_y"]; + NSString *cropStyle = call.arguments[@"crop_style"]; + NSArray *aspectRatioPresets = call.arguments[@"aspect_ratio_presets"]; + NSNumber *compressQuality = call.arguments[@"compress_quality"]; + NSString *compressFormat = call.arguments[@"compress_format"]; + + UIImage *image = [UIImage imageWithContentsOfFile:sourcePath]; + TOCropViewController *cropViewController; + + if ([@"circle" isEqualToString:cropStyle]) { + cropViewController = [[TOCropViewController alloc] initWithCroppingStyle:TOCropViewCroppingStyleCircular image:image]; + } else { + cropViewController = [[TOCropViewController alloc] initWithImage:image]; + } + + cropViewController.delegate = self; + + if (compressQuality && [compressQuality isKindOfClass:[NSNumber class]]) { + _compressQuality = compressQuality.intValue * 1.0f / 100; + } else { + _compressQuality = 0.9f; + } + if (compressFormat && [compressFormat isKindOfClass:[NSString class]]) { + _compressFormat = compressFormat; + } else { + _compressFormat = @"jpg"; + } + + NSMutableArray *allowedAspectRatios = [NSMutableArray new]; + for (NSString *preset in aspectRatioPresets) { + if (preset) { + [allowedAspectRatios addObject:@([self parseAspectRatioPresetFromName:preset])]; + } + } + cropViewController.allowedAspectRatios = allowedAspectRatios; + + if (ratioX != (id)[NSNull null] && ratioY != (id)[NSNull null]) { + cropViewController.customAspectRatio = CGSizeMake([ratioX floatValue], [ratioY floatValue]); + cropViewController.resetAspectRatioEnabled = NO; + cropViewController.aspectRatioPickerButtonHidden = YES; + cropViewController.aspectRatioLockDimensionSwapEnabled = YES; + cropViewController.aspectRatioLockEnabled = YES; + } + + [self setupUiCustomizedOptions:call.arguments forViewController:cropViewController]; + + UIWindow *window = [UIApplication sharedApplication].delegate.window; + if (!window && @available(iOS 13.0, *)) { + for (UIWindowScene* scene in [UIApplication sharedApplication].connectedScenes) { + if (scene.activationState == UISceneActivationStateForegroundActive) { + for (UIWindow *w in scene.windows) { + if (w.isKeyWindow) { + window = w; + break; + } + } + } + } + } + + [window.rootViewController presentViewController:cropViewController animated:YES completion:nil]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)setupUiCustomizedOptions:(id)options forViewController:(TOCropViewController*)controller { + NSNumber *minimumAspectRatio = options[@"ios.minimum_aspect_ratio"]; + NSNumber *rectX = options[@"ios.rect_x"]; + NSNumber *rectY = options[@"ios.rect_y"]; + NSNumber *rectWidth = options[@"ios.rect_width"]; + NSNumber *rectHeight = options[@"ios.rect_height"]; + NSNumber *showActivitySheetOnDone = options[@"ios.show_activity_sheet_on_done"]; + NSNumber *showCancelConfirmationDialog = options[@"ios.show_cancel_confirmation_dialog"]; + NSNumber *rotateClockwiseButtonHidden = options[@"ios.rotate_clockwise_button_hidden"]; + NSNumber *hidesNavigationBar = options[@"ios.hides_navigation_bar"]; + NSNumber *rotateButtonHidden = options[@"ios.rotate_button_hidden"]; + NSNumber *resetButtonHidden = options[@"ios.reset_button_hidden"]; + NSNumber *aspectRatioPickerButtonHidden = options[@"ios.aspect_ratio_picker_button_hidden"]; + NSNumber *resetAspectRatioEnabled = options[@"ios.reset_aspect_ratio_enabled"]; + NSNumber *aspectRatioLockDimensionSwapEnabled = options[@"ios.aspect_ratio_lock_dimension_swap_enabled"]; + NSNumber *aspectRatioLockEnabled = options[@"ios.aspect_ratio_lock_enabled"]; + NSString *title = options[@"ios.title"]; + NSString *doneButtonTitle = options[@"ios.done_button_title"]; + NSString *cancelButtonTitle = options[@"ios.cancel_button_title"]; + + if (minimumAspectRatio && [minimumAspectRatio isKindOfClass:[NSNumber class]]) { + controller.minimumAspectRatio = minimumAspectRatio.floatValue; + } + if (rectX && [rectX isKindOfClass:[NSNumber class]] + && rectY && [rectY isKindOfClass:[NSNumber class]] + && rectWidth && [rectWidth isKindOfClass:[NSNumber class]] + && rectHeight && [rectHeight isKindOfClass:[NSNumber class]]) { + controller.imageCropFrame = CGRectMake(rectX.floatValue, rectY.floatValue, rectWidth.floatValue, rectHeight.floatValue); + } + if (showActivitySheetOnDone && [showActivitySheetOnDone isKindOfClass:[NSNumber class]]) { + controller.showActivitySheetOnDone = showActivitySheetOnDone.boolValue; + } + if (showCancelConfirmationDialog && [showCancelConfirmationDialog isKindOfClass:[NSNumber class]]) { + controller.showCancelConfirmationDialog = showCancelConfirmationDialog.boolValue; + } + if (rotateClockwiseButtonHidden && [rotateClockwiseButtonHidden isKindOfClass:[NSNumber class]]) { + controller.rotateClockwiseButtonHidden = rotateClockwiseButtonHidden.boolValue; + } + if (hidesNavigationBar && [hidesNavigationBar isKindOfClass:[NSNumber class]]) { + controller.hidesNavigationBar = hidesNavigationBar.boolValue; + } + if (rotateButtonHidden && [rotateButtonHidden isKindOfClass:[NSNumber class]]) { + controller.rotateButtonsHidden = rotateButtonHidden.boolValue; + } + if (resetButtonHidden && [resetButtonHidden isKindOfClass:[NSNumber class]]) { + controller.resetButtonHidden = resetButtonHidden.boolValue; + } + if (aspectRatioPickerButtonHidden && [aspectRatioPickerButtonHidden isKindOfClass:[NSNumber class]]) { + controller.aspectRatioPickerButtonHidden = aspectRatioPickerButtonHidden.boolValue; + } + if (resetAspectRatioEnabled && [resetAspectRatioEnabled isKindOfClass:[NSNumber class]]) { + controller.resetAspectRatioEnabled = resetAspectRatioEnabled.boolValue; + } + if (aspectRatioLockDimensionSwapEnabled && [aspectRatioLockDimensionSwapEnabled isKindOfClass:[NSNumber class]]) { + controller.aspectRatioLockDimensionSwapEnabled = aspectRatioLockDimensionSwapEnabled.boolValue; + } + if (aspectRatioLockEnabled && [aspectRatioLockEnabled isKindOfClass:[NSNumber class]]) { + controller.aspectRatioLockEnabled = aspectRatioLockEnabled.boolValue; + } + if (title && [title isKindOfClass:[NSString class]]) { + controller.title = title; + } + if (doneButtonTitle && [doneButtonTitle isKindOfClass:[NSString class]]) { + controller.doneButtonTitle = doneButtonTitle; + } + if (cancelButtonTitle && [cancelButtonTitle isKindOfClass:[NSString class]]) { + controller.cancelButtonTitle = cancelButtonTitle; + } +} + +- (TOCropViewControllerAspectRatioPreset)parseAspectRatioPresetFromName:(NSString*)name { + if ([@"square" isEqualToString:name]) { + return TOCropViewControllerAspectRatioPresetSquare; + } else if ([@"original" isEqualToString:name]) { + return TOCropViewControllerAspectRatioPresetOriginal; + } else if ([@"3x2" isEqualToString:name]) { + return TOCropViewControllerAspectRatioPreset3x2; + } else if ([@"4x3" isEqualToString:name]) { + return TOCropViewControllerAspectRatioPreset4x3; + } else if ([@"5x3" isEqualToString:name]) { + return TOCropViewControllerAspectRatioPreset5x3; + } else if ([@"5x4" isEqualToString:name]) { + return TOCropViewControllerAspectRatioPreset5x4; + } else if ([@"7x5" isEqualToString:name]) { + return TOCropViewControllerAspectRatioPreset7x5; + } else if ([@"16x9" isEqualToString:name]) { + return TOCropViewControllerAspectRatioPreset16x9; + } else { + return TOCropViewControllerAspectRatioPresetOriginal; + } +} + +# pragma TOCropViewControllerDelegate + +- (void)cropViewController:(TOCropViewController *)cropViewController didCropToImage:(UIImage *)image withRect:(CGRect)cropRect angle:(NSInteger)angle +{ + image = [self normalizedImage:image]; + + NSNumber *maxWidth = [_arguments objectForKey:@"max_width"]; + NSNumber *maxHeight = [_arguments objectForKey:@"max_height"]; + + if (maxWidth != (id)[NSNull null] && maxHeight != (id)[NSNull null]) { + image = [self scaledImage:image maxWidth:maxWidth maxHeight:maxHeight]; + } + + NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; + + NSData *data; + NSString *tmpFile; + + if ([@"png" isEqualToString:_compressFormat]) { + data = UIImagePNGRepresentation(image); + tmpFile = [NSString stringWithFormat:@"image_cropper_%@.png", guid]; + } else { + data = UIImageJPEGRepresentation(image, _compressQuality); + tmpFile = [NSString stringWithFormat:@"image_cropper_%@.jpg", guid]; + } + + NSString *tmpDirectory = NSTemporaryDirectory(); + NSString *tmpPath = [tmpDirectory stringByAppendingPathComponent:tmpFile]; + + if (_result) { + if ([[NSFileManager defaultManager] createFileAtPath:tmpPath contents:data attributes:nil]) { + _result(tmpPath); + } else { + _result([FlutterError errorWithCode:@"create_error" + message:@"Temporary file could not be created" + details:nil]); + } + + [cropViewController dismissViewControllerAnimated:YES completion:nil]; + + _result = nil; + _arguments = nil; + } +} + +- (void)cropViewController:(TOCropViewController *)cropViewController didFinishCancelled:(BOOL)cancelled { + [cropViewController dismissViewControllerAnimated:YES completion:nil]; + _result(nil); + + _result = nil; + _arguments = nil; +} + +// The way we save images to the tmp dir currently throws away all EXIF data +// (including the orientation of the image). That means, pics taken in portrait +// will not be orientated correctly as is. To avoid that, we rotate the actual +// image data. +// TODO(goderbauer): investigate how to preserve EXIF data. +- (UIImage *)normalizedImage:(UIImage *)image { + if (image.imageOrientation == UIImageOrientationUp) return image; + + UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale); + [image drawInRect:(CGRect){0, 0, image.size}]; + UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return normalizedImage; +} + +- (UIImage *)scaledImage:(UIImage *)image + maxWidth:(NSNumber *)maxWidth + maxHeight:(NSNumber *)maxHeight { + double originalWidth = image.size.width; + double originalHeight = image.size.height; + + bool hasMaxWidth = maxWidth != (id)[NSNull null]; + bool hasMaxHeight = maxHeight != (id)[NSNull null]; + + double width = hasMaxWidth ? MIN([maxWidth doubleValue], originalWidth) : originalWidth; + double height = hasMaxHeight ? MIN([maxHeight doubleValue], originalHeight) : originalHeight; + + bool shouldDownscaleWidth = hasMaxWidth && [maxWidth doubleValue] < originalWidth; + bool shouldDownscaleHeight = hasMaxHeight && [maxHeight doubleValue] < originalHeight; + bool shouldDownscale = shouldDownscaleWidth || shouldDownscaleHeight; + + if (shouldDownscale) { + double downscaledWidth = (height / originalHeight) * originalWidth; + double downscaledHeight = (width / originalWidth) * originalHeight; + + if (width < height) { + if (!hasMaxWidth) { + width = downscaledWidth; + } else { + height = downscaledHeight; + } + } else if (height < width) { + if (!hasMaxHeight) { + height = downscaledHeight; + } else { + width = downscaledWidth; + } + } else { + if (originalWidth < originalHeight) { + width = downscaledWidth; + } else if (originalHeight < originalWidth) { + height = downscaledHeight; + } + } + } + + UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 1.0); + [image drawInRect:CGRectMake(0, 0, width, height)]; + + UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return scaledImage; +} + +@end diff --git a/local_packages/image_cropper-5.0.1/ios/image_cropper.podspec b/local_packages/image_cropper-5.0.1/ios/image_cropper.podspec new file mode 100644 index 0000000..d67059b --- /dev/null +++ b/local_packages/image_cropper-5.0.1/ios/image_cropper.podspec @@ -0,0 +1,22 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'image_cropper' + s.version = '0.0.4' + s.summary = 'A Flutter plugin supports cropping images' + s.description = <<-DESC +A Flutter plugin supports cropping images + DESC + s.homepage = 'https://github.com/hnvn/flutter_image_cropper' + s.license = { :file => '../LICENSE' } + s.author = { 'HungHD' => 'hunghd.yb@gmail.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + s.dependency 'TOCropViewController', '~> 2.6.1' + + s.ios.deployment_target = '11.0' +end + diff --git a/local_packages/image_cropper-5.0.1/lib/image_cropper.dart b/local_packages/image_cropper-5.0.1/lib/image_cropper.dart new file mode 100644 index 0000000..413e3c9 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/lib/image_cropper.dart @@ -0,0 +1,32 @@ +/// +/// * author: Hung Duy Ha (hunghd) +/// * email: hunghd.yb@gmail.com +/// +/// A plugin provides capability of manipulating an image including rotating +/// and cropping. +/// +/// Note that: this plugin is based on different native libraries depending on +/// Android or iOS platform, so it shows different UI look and feel between +/// those platforms. +/// + +export 'package:image_cropper_platform_interface/image_cropper_platform_interface.dart' + show + CropAspectRatioPreset, + CropStyle, + ImageCompressFormat, + CropAspectRatio, + CroppedFile, + PlatformUiSettings, + WebTranslations, + AndroidUiSettings, + IOSUiSettings, + CropperDialogBuilder, + CropperRouteBuilder, + CropperPresentStyle, + WebUiSettings, + CroppieViewPort, + CroppieBoundary, + RotationAngle; + +export 'src/cropper.dart'; diff --git a/local_packages/image_cropper-5.0.1/lib/src/cropper.dart b/local_packages/image_cropper-5.0.1/lib/src/cropper.dart new file mode 100644 index 0000000..b6b953a --- /dev/null +++ b/local_packages/image_cropper-5.0.1/lib/src/cropper.dart @@ -0,0 +1,142 @@ +// Copyright 2013, the Dart project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:image_cropper_platform_interface/image_cropper_platform_interface.dart'; + +/// +/// A convenient class wraps all api functions of **ImageCropper** plugin +/// +class ImageCropper { + static ImageCropperPlatform get platform => ImageCropperPlatform.instance; + + /// + /// Launch cropper UI for an image. + /// + /// + /// **parameters:** + /// + /// * sourcePath: the absolute path of an image file. + /// + /// * maxWidth: maximum cropped image width. Note: this field is ignored on Web. + /// + /// * maxHeight: maximum cropped image height. Note: this field is ignored on Web + /// + /// * aspectRatio: controls the aspect ratio of crop bounds. If this values is set, + /// the cropper is locked and user can't change the aspect ratio of crop bounds. + /// Note: this field is ignored on Web + /// + /// * aspectRatioPresets: controls the list of aspect ratios in the crop menu view. + /// In Android, you can set the initialized aspect ratio when starting the cropper + /// by setting the value of [AndroidUiSettings.initAspectRatio]. Default is a list of + /// [CropAspectRatioPreset.original], [CropAspectRatioPreset.square], + /// [CropAspectRatioPreset.ratio3x2], [CropAspectRatioPreset.ratio4x3] and + /// [CropAspectRatioPreset.ratio16x9]. + /// Note: this field is ignored on Web + /// + /// * cropStyle: controls the style of crop bounds, it can be rectangle or + /// circle style (default is [CropStyle.rectangle]). + /// Note: on Web, this field can be overrided by [WebUiSettings.viewPort.type] + /// + /// * compressFormat: the format of result image, png or jpg (default is [ImageCompressFormat.jpg]) + /// + /// * compressQuality: the value [0 - 100] to control the quality of image compression + /// + /// * uiSettings: controls UI customization on specific platform (android, ios, web,...) + /// + /// See: + /// * [AndroidUiSettings] controls UI customization for Android + /// * [IOSUiSettings] controls UI customization for iOS + /// + /// **return:** + /// + /// A result file of the cropped image. + /// + /// **Note:** + /// + /// * The result file is saved in NSTemporaryDirectory on iOS and application Cache directory + /// on Android, so it can be lost later, you are responsible for storing it somewhere + /// permanent (if needed). + /// + /// * The implementation on Web is much different compared to the implementation on mobile app. + /// It causes some configuration fields not working (maxWidth, maxHeight, aspectRatio, + /// aspectRatioPresets) and [WebUiSettings] is required for Web. + /// + Future cropImage({ + required String sourcePath, + int? maxWidth, + int? maxHeight, + CropAspectRatio? aspectRatio, + List aspectRatioPresets = const [ + CropAspectRatioPreset.original, + CropAspectRatioPreset.square, + CropAspectRatioPreset.ratio3x2, + CropAspectRatioPreset.ratio4x3, + CropAspectRatioPreset.ratio16x9 + ], + CropStyle cropStyle = CropStyle.rectangle, + ImageCompressFormat compressFormat = ImageCompressFormat.jpg, + int compressQuality = 90, + List? uiSettings, + }) { + return platform.cropImage( + sourcePath: sourcePath, + maxWidth: maxWidth, + maxHeight: maxHeight, + aspectRatio: aspectRatio, + aspectRatioPresets: aspectRatioPresets, + cropStyle: cropStyle, + compressFormat: compressFormat, + compressQuality: compressQuality, + uiSettings: uiSettings, + ); + } + + /// + /// Retrieve cropped image lost due to activity termination (Android only). + /// This method works similarly to [retrieveLostData] method from [image_picker] + /// library. Unlike [retrieveLostData], does not throw an error on other platforms, + /// but returns null result. + /// + /// [recoverImage] as (well as [retrieveLostData]) will return value on any + /// call after a successful [cropImage], so you can potentially get unexpected + /// result when using [ImageCropper] in different layout. Recommended usage comes down to + /// this: + /// + /// ```dart + /// void crop() async { + /// final cropper = ImageCropper(); + /// final croppedFile = await cropper.cropImage(/* your parameters */); + /// // At this point we know that the main activity did survive and we can + /// // discard the cached value + /// await cropper.recoverImage(); + /// // process croppedFile value + /// } + /// + /// @override + /// void initState() { + /// _getLostData(); + /// super.initState(); + /// } + /// + /// void _getLostData() async { + /// final recoveredCroppedImage = await ImageCropper().recoverImage(); + /// if (recoveredCroppedImage != null) { + /// // process recoveredCroppedImage value + /// } + /// } + /// ``` + /// + /// **return:** + /// + /// A result file of the cropped image. + /// + /// See also: + /// * [Android Activity Lifecycle](https://developer.android.com/reference/android/app/Activity.html) + /// + Future recoverImage() { + return platform.recoverImage(); + } +} diff --git a/local_packages/image_cropper-5.0.1/pubspec.yaml b/local_packages/image_cropper-5.0.1/pubspec.yaml new file mode 100644 index 0000000..2554eb5 --- /dev/null +++ b/local_packages/image_cropper-5.0.1/pubspec.yaml @@ -0,0 +1,30 @@ +name: image_cropper +description: A Flutter plugin for Android, iOS and Web supports cropping images +version: 5.0.1 +homepage: https://github.com/hnvn/flutter_image_cropper + +environment: + sdk: '>=2.18.0 <4.0.0' + flutter: '>=3.3.0' + +flutter: + plugin: + platforms: + android: + package: vn.hunghd.flutter.plugins.imagecropper + pluginClass: ImageCropperPlugin + ios: + pluginClass: FLTImageCropperPlugin + web: + default_package: image_cropper_for_web + +dependencies: + flutter: + sdk: flutter + image_cropper_platform_interface: ^5.0.0 + image_cropper_for_web: ^3.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + plugin_platform_interface: ^2.0.0 \ No newline at end of file diff --git a/local_packages/image_cropper-5.0.1/screenshots/android_demo.gif b/local_packages/image_cropper-5.0.1/screenshots/android_demo.gif new file mode 100644 index 0000000..b632e21 Binary files /dev/null and b/local_packages/image_cropper-5.0.1/screenshots/android_demo.gif differ diff --git a/local_packages/image_cropper-5.0.1/screenshots/croppie_preview.png b/local_packages/image_cropper-5.0.1/screenshots/croppie_preview.png new file mode 100644 index 0000000..b6d3e4e Binary files /dev/null and b/local_packages/image_cropper-5.0.1/screenshots/croppie_preview.png differ diff --git a/local_packages/image_cropper-5.0.1/screenshots/ios_demo.gif b/local_packages/image_cropper-5.0.1/screenshots/ios_demo.gif new file mode 100644 index 0000000..25513ae Binary files /dev/null and b/local_packages/image_cropper-5.0.1/screenshots/ios_demo.gif differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/CHANGELOG.md b/local_packages/loading_indicator_view_plus-2.0.0/CHANGELOG.md new file mode 100644 index 0000000..ea9c9c4 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/CHANGELOG.md @@ -0,0 +1,11 @@ +## 2.0.0 + +* 升级支持空安全 + +## 1.1.0 + +* 优化项目,去除无用代码,添加使用文档 + +## 1.0.0 + +* 完成 24 个 loading widget \ No newline at end of file diff --git a/local_packages/loading_indicator_view_plus-2.0.0/LICENSE b/local_packages/loading_indicator_view_plus-2.0.0/LICENSE new file mode 100644 index 0000000..aed7461 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/LICENSE @@ -0,0 +1,13 @@ +Copyright 2019 Hitomis, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/local_packages/loading_indicator_view_plus-2.0.0/README.md b/local_packages/loading_indicator_view_plus-2.0.0/README.md new file mode 100644 index 0000000..0facb1f --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/README.md @@ -0,0 +1,43 @@ +# loading_indicator_view_plus + +A collection of awesome flutter loading animation + +forked from Hitomis/loading_indicator_view + +## Demo + +![image](https://github.com/xtcel/loading_indicator_view_plus/blob/master/flutter_indicator_view.gif) + +## Usage + +``` +loading_indicator_view_plus: ^2.0.0 +``` + + +## Animation types + +| Type | Type | Type | Type | +|---|---|---|---| +|1. LineSpinFadeLoader | 2. BallBeat | 3. BallClipRotateMultiple | 4. BallGridPulse | +|5. LineScale | 6. BallPulseRise | 7. BallScaleRippleMultiple | 8. BallZigZag | +|9. BallScale | 10. BallPulseSync| 11. BallScaleMultiple | 12. BallPulse | +|13. BallClipRotatePulse | 14. BallGridBeat | 15. SquareSpin | 16. BallSpinFadeLoader | +|17. BallScaleRipple | 18. SemiCircleSpin | 19. LineScalePulseOut | 20. BallClipRotate | +|21. Pacman | 22. BallRotate | 23. CubeTransition | 24. TriangleSkewSpin| + + +# Licence + Copyright 2019 Hitomis, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/local_packages/loading_indicator_view_plus-2.0.0/analysis_options.yaml b/local_packages/loading_indicator_view_plus-2.0.0/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/local_packages/loading_indicator_view_plus-2.0.0/android/build.gradle b/local_packages/loading_indicator_view_plus-2.0.0/android/build.gradle new file mode 100644 index 0000000..2dd6d65 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/android/build.gradle @@ -0,0 +1,47 @@ +group 'com.xtcel.loading_indicator_view_plus.loading_indicator_view_plus' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '2.2.20' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.11.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + namespace 'com.xtcel.loading_indicator_view_plus.loading_indicator_view_plus' + compileSdkVersion 31 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + minSdkVersion 16 + } +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/android/settings.gradle b/local_packages/loading_indicator_view_plus-2.0.0/android/settings.gradle new file mode 100644 index 0000000..d89128d --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'loading_indicator_view_plus' diff --git a/local_packages/loading_indicator_view_plus-2.0.0/android/src/main/AndroidManifest.xml b/local_packages/loading_indicator_view_plus-2.0.0/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b3561cb --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/android/src/main/kotlin/com/xtcel/loading_indicator_view_plus/loading_indicator_view_plus/LoadingIndicatorViewPlusPlugin.kt b/local_packages/loading_indicator_view_plus-2.0.0/android/src/main/kotlin/com/xtcel/loading_indicator_view_plus/loading_indicator_view_plus/LoadingIndicatorViewPlusPlugin.kt new file mode 100644 index 0000000..6703532 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/android/src/main/kotlin/com/xtcel/loading_indicator_view_plus/loading_indicator_view_plus/LoadingIndicatorViewPlusPlugin.kt @@ -0,0 +1,35 @@ +package com.xtcel.loading_indicator_view_plus.loading_indicator_view_plus + +import androidx.annotation.NonNull + +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +/** LoadingIndicatorViewPlusPlugin */ +class LoadingIndicatorViewPlusPlugin: FlutterPlugin, MethodCallHandler { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel : MethodChannel + + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "loading_indicator_view_plus") + channel.setMethodCallHandler(this) + } + + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + if (call.method == "getPlatformVersion") { + result.success("Android ${android.os.Build.VERSION.RELEASE}") + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/README.md b/local_packages/loading_indicator_view_plus-2.0.0/example/README.md new file mode 100644 index 0000000..379993d --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/README.md @@ -0,0 +1,16 @@ +# loading_indicator_view_plus_example + +Demonstrates how to use the loading_indicator_view_plus plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/analysis_options.yaml b/local_packages/loading_indicator_view_plus-2.0.0/example/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/build.gradle b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/build.gradle new file mode 100644 index 0000000..fdbb61f --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/build.gradle @@ -0,0 +1,71 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.xtcel.loading_indicator_view_plus.loading_indicator_view_plus_example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/debug/AndroidManifest.xml b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..b824ded --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/AndroidManifest.xml b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1882cad --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/kotlin/com/xtcel/loading_indicator_view_plus/loading_indicator_view_plus_example/MainActivity.kt b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/kotlin/com/xtcel/loading_indicator_view_plus/loading_indicator_view_plus_example/MainActivity.kt new file mode 100644 index 0000000..bb72a6e --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/kotlin/com/xtcel/loading_indicator_view_plus/loading_indicator_view_plus_example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.xtcel.loading_indicator_view_plus.loading_indicator_view_plus_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/drawable-v21/launch_background.xml b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/drawable/launch_background.xml b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/values-night/styles.xml b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/values/styles.xml b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/profile/AndroidManifest.xml b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..b824ded --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/build.gradle b/local_packages/loading_indicator_view_plus-2.0.0/example/android/build.gradle new file mode 100644 index 0000000..83ae220 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/gradle.properties b/local_packages/loading_indicator_view_plus-2.0.0/example/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/gradle/wrapper/gradle-wrapper.properties b/local_packages/loading_indicator_view_plus-2.0.0/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..cb24abd --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/android/settings.gradle b/local_packages/loading_indicator_view_plus-2.0.0/example/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Flutter/AppFrameworkInfo.plist b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Flutter/Debug.xcconfig b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Flutter/Release.xcconfig b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Podfile b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Podfile new file mode 100644 index 0000000..88359b2 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Podfile.lock b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Podfile.lock new file mode 100644 index 0000000..9c3e965 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - Flutter (1.0.0) + - loading_indicator_view_plus (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - loading_indicator_view_plus (from `.symlinks/plugins/loading_indicator_view_plus/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + loading_indicator_view_plus: + :path: ".symlinks/plugins/loading_indicator_view_plus/ios" + +SPEC CHECKSUMS: + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + loading_indicator_view_plus: 6ab04899364e9e9a77c7811f831504b5ee1b316d + +PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 + +COCOAPODS: 1.11.3 diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/project.pbxproj b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..60ae971 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,549 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 6ADA3E43FDB1A970A93A14F9 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C410E6B4A7859606ECD3E2E /* Pods_Runner.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 20469ACDDDBA5A528F3B4D6E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 5C410E6B4A7859606ECD3E2E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C37820DC44ED4DC16F63A657 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + CEFEF146D5623087A49AD7CD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6ADA3E43FDB1A970A93A14F9 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + E8835A36FC83A952D14D077C /* Pods */, + D0491F0BE124C3146A40D5D0 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + D0491F0BE124C3146A40D5D0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5C410E6B4A7859606ECD3E2E /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E8835A36FC83A952D14D077C /* Pods */ = { + isa = PBXGroup; + children = ( + C37820DC44ED4DC16F63A657 /* Pods-Runner.debug.xcconfig */, + CEFEF146D5623087A49AD7CD /* Pods-Runner.release.xcconfig */, + 20469ACDDDBA5A528F3B4D6E /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 4D9F49BED7A1CA6D78D87834 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 4FA3173FD3BE28EB56DABC08 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 4D9F49BED7A1CA6D78D87834 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 4FA3173FD3BE28EB56DABC08 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.xtcel.loadingindicatorviewplus.loadingIndicatorViewPlusExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.xtcel.loadingindicatorviewplus.loadingIndicatorViewPlusExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.xtcel.loadingindicatorviewplus.loadingIndicatorViewPlusExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c87d15a --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/AppDelegate.swift b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Base.lproj/Main.storyboard b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Info.plist b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Info.plist new file mode 100644 index 0000000..82dffab --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Loading Indicator View Plus + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + loading_indicator_view_plus_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Runner-Bridging-Header.h b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/lib/main.dart b/local_packages/loading_indicator_view_plus-2.0.0/example/lib/main.dart new file mode 100644 index 0000000..a40542c --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/lib/main.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:loading_indicator_view_plus/loading_indicator_view_plus.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + debugShowCheckedModeBanner: false, + theme: ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primarySwatch: Colors.blue, + ), + home: const MyHomePage(title: 'Loading indicator view plus Demo'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key, required this.title}) : super(key: key); + + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _orderNum = 1; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color.fromARGB(255, 185, 63, 81), + appBar: AppBar( + title: Text(widget.title), + ), + body: GridView( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + childAspectRatio: 1.0, + ), + children: [ + wrapOrder(Center(child: LineSpinFadeLoaderIndicator())), + wrapOrder(Center(child: BallBeatIndicator())), + wrapOrder(Center(child: BallClipRotateMultipleIndicator())), + wrapOrder(Center(child: BallGridPulseIndicator())), + wrapOrder(Center(child: LineScaleIndicator())), + wrapOrder(Center(child: BallPulseRiseIndicator())), + wrapOrder(Center(child: BallScaleRippleMultipleIndicator())), + wrapOrder(Center(child: BallZigZagIndicator())), + wrapOrder(Center(child: BallScaleIndicator())), + wrapOrder(Center(child: BallPulseSyncIndicator())), + wrapOrder(Center(child: BallScaleMultipleIndicator())), + wrapOrder(Center(child: BallPulseIndicator())), + wrapOrder(Center(child: BallClipRotatePulseIndicator())), + wrapOrder(Center(child: BallGridBeatIndicator())), + wrapOrder(Center(child: SquareSpinIndicator())), + wrapOrder(Center(child: BallSpinFadeLoaderIndicator())), + wrapOrder(Center(child: BallScaleRippleIndicator())), + wrapOrder(Center(child: SemiCircleSpinIndicator())), + wrapOrder(Center(child: LineScalePulseOutIndicator())), + wrapOrder(Center(child: BallClipRotateIndicator())), + wrapOrder(Center(child: PacmanIndicator())), + wrapOrder(Center(child: BallRotateIndicator())), + wrapOrder(Center(child: CubeTransitionIndicator())), + wrapOrder(Center(child: TriangleSkewSpinIndicator())), + ], + ), + ); + } + + @override + void didUpdateWidget(MyHomePage oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget != widget) { + _orderNum = 1; + } + } + + Widget wrapContainer(Widget child, [Color backgroundColor = Colors.green]) => + Container(color: backgroundColor, child: child); + + Widget wrapOrder(Widget child) => Stack(children: [ + child, + Positioned( + left: 8, + bottom: 0, + child: Text("${_orderNum++}", + style: const TextStyle(color: Colors.white, fontSize: 18)), + ), + ]); +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/pubspec.yaml b/local_packages/loading_indicator_view_plus-2.0.0/example/pubspec.yaml new file mode 100644 index 0000000..66e70d4 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/pubspec.yaml @@ -0,0 +1,84 @@ +name: loading_indicator_view_plus_example +description: Demonstrates how to use the loading_indicator_view_plus plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +# publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: '>=2.18.0 <3.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + loading_indicator_view_plus: + # When depending on this package from a real application you should use: + # loading_indicator_view_plus: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/local_packages/loading_indicator_view_plus-2.0.0/example/test/widget_test.dart b/local_packages/loading_indicator_view_plus-2.0.0/example/test/widget_test.dart new file mode 100644 index 0000000..795d393 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/example/test/widget_test.dart @@ -0,0 +1,29 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:loading_indicator_view_plus_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyHomePage( + title: "test", + )); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => + widget is Text && widget.data!.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/flutter_indicator_view.gif b/local_packages/loading_indicator_view_plus-2.0.0/flutter_indicator_view.gif new file mode 100644 index 0000000..4e529d8 Binary files /dev/null and b/local_packages/loading_indicator_view_plus-2.0.0/flutter_indicator_view.gif differ diff --git a/local_packages/loading_indicator_view_plus-2.0.0/ios/Classes/LoadingIndicatorViewPlusPlugin.h b/local_packages/loading_indicator_view_plus-2.0.0/ios/Classes/LoadingIndicatorViewPlusPlugin.h new file mode 100644 index 0000000..ad207d5 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/ios/Classes/LoadingIndicatorViewPlusPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface LoadingIndicatorViewPlusPlugin : NSObject +@end diff --git a/local_packages/loading_indicator_view_plus-2.0.0/ios/Classes/LoadingIndicatorViewPlusPlugin.m b/local_packages/loading_indicator_view_plus-2.0.0/ios/Classes/LoadingIndicatorViewPlusPlugin.m new file mode 100644 index 0000000..22c9302 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/ios/Classes/LoadingIndicatorViewPlusPlugin.m @@ -0,0 +1,15 @@ +#import "LoadingIndicatorViewPlusPlugin.h" +#if __has_include() +#import +#else +// Support project import fallback if the generated compatibility header +// is not copied when this plugin is created as a library. +// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 +#import "loading_indicator_view_plus-Swift.h" +#endif + +@implementation LoadingIndicatorViewPlusPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftLoadingIndicatorViewPlusPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/local_packages/loading_indicator_view_plus-2.0.0/ios/Classes/SwiftLoadingIndicatorViewPlusPlugin.swift b/local_packages/loading_indicator_view_plus-2.0.0/ios/Classes/SwiftLoadingIndicatorViewPlusPlugin.swift new file mode 100644 index 0000000..0f73e48 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/ios/Classes/SwiftLoadingIndicatorViewPlusPlugin.swift @@ -0,0 +1,14 @@ +import Flutter +import UIKit + +public class SwiftLoadingIndicatorViewPlusPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "loading_indicator_view_plus", binaryMessenger: registrar.messenger()) + let instance = SwiftLoadingIndicatorViewPlusPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + result("iOS " + UIDevice.current.systemVersion) + } +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/ios/loading_indicator_view_plus.podspec b/local_packages/loading_indicator_view_plus-2.0.0/ios/loading_indicator_view_plus.podspec new file mode 100644 index 0000000..08ccf4c --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/ios/loading_indicator_view_plus.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint loading_indicator_view_plus.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'loading_indicator_view_plus' + s.version = '0.0.1' + s.summary = 'A new Flutter plugin project.' + s.description = <<-DESC +A new Flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '9.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' +end diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/loading_indicator_view_plus.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/loading_indicator_view_plus.dart new file mode 100644 index 0000000..0d83958 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/loading_indicator_view_plus.dart @@ -0,0 +1,25 @@ +export 'src/indicators/ball_beat_indicator.dart'; +export 'src/indicators/ball_clip_rotate_indicator.dart'; +export 'src/indicators/ball_clip_rotate_multiple_indicator.dart'; +export 'src/indicators/ball_clip_rotate_pulse_indicator.dart'; +export 'src/indicators/ball_grid_beat_indicator.dart'; +export 'src/indicators/ball_grid_pulse_indicator.dart'; +export 'src/indicators/ball_pulse_indicator.dart'; +export 'src/indicators/ball_pulse_rise_indicator.dart'; +export 'src/indicators/ball_pulse_sync_indicator.dart'; +export 'src/indicators/ball_rotate_indicator.dart'; +export 'src/indicators/ball_scale_indicator.dart'; +export 'src/indicators/ball_scale_multiple_indicator.dart'; +export 'src/indicators/ball_scale_ripple_indicator.dart'; +export 'src/indicators/ball_scale_ripple_multiple_indicator.dart'; +export 'src/indicators/ball_spin_fade_loader_indicator.dart'; +export 'src/indicators/ball_zig_zag_indicator.dart'; +export 'src/indicators/cube_transition_indicator.dart'; +export 'src/indicators/line_scale_indicator.dart'; +export 'src/indicators/line_scale_pulse_out_indicator.dart'; +export 'src/indicators/line_scale_pulse_out_rapid_indicator.dart'; +export 'src/indicators/line_spin_fade_loader_indicator.dart'; +export 'src/indicators/pacman_indicator.dart'; +export 'src/indicators/semi_circle_spin_indicator.dart'; +export 'src/indicators/square_spin_indicator.dart'; +export 'src/indicators/triangle_skew_spin_indicator.dart'; diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_beat_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_beat_indicator.dart new file mode 100644 index 0000000..3387a35 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_beat_indicator.dart @@ -0,0 +1,146 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:loading_indicator_view_plus/src/infinite_progress.dart'; + +/// +/// author: Vans Z +/// date: 2019-05-31 +/// + +class BallBeatIndicator extends StatefulWidget { + BallBeatIndicator({ + this.minAlpha: 51, + this.maxAlpha: 255, + this.minRadius: 5.6, + this.maxRadius: 7.2, + this.spacing: 3, + this.ballColor: Colors.white, + this.duration: const Duration(milliseconds: 400), + }); + + final int minAlpha; + final int maxAlpha; + final double minRadius; + final double maxRadius; + final double spacing; + final Color ballColor; + final Duration duration; + + @override + State createState() => _BallBeatIndicatorState(); +} + +class _BallBeatIndicatorState extends State + with SingleTickerProviderStateMixin, InfiniteProgressMixin { + @override + void initState() { + startEngine(this, widget.duration); + super.initState(); + } + + @override + void dispose() { + closeEngine(); + super.dispose(); + } + + @override + Size measureSize() { + var width = widget.maxRadius * 2 * 3 + widget.spacing * 2; + var height = widget.maxRadius * 2; + return Size(width, height); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + return CustomPaint( + size: measureSize(), + painter: _BallBeatIndicatorPainter( + animationValue: animationValue, + minRadius: widget.minRadius, + maxRadius: widget.maxRadius, + minAlpha: widget.minAlpha, + maxAlpha: widget.maxAlpha, + spacing: widget.spacing, + ballColor: widget.ballColor, + ), + ); + }, + ); + } +} + +double progress = .0; +double lastExtent = .0; + +class _BallBeatIndicatorPainter extends CustomPainter { + _BallBeatIndicatorPainter({ + required this.animationValue, + required this.minRadius, + required this.maxRadius, + required this.minAlpha, + required this.maxAlpha, + required this.spacing, + required this.ballColor, + }) : alphaList = [ + minAlpha + (maxAlpha - minAlpha) * 0.9, + minAlpha + (maxAlpha - minAlpha) * 0.1, + minAlpha + (maxAlpha - minAlpha) * 0.9, + ], + radiusList = [ + minRadius + (maxRadius - minRadius) * 0.9, + minRadius + (maxRadius - minRadius) * 0.63, + minRadius + (maxRadius - minRadius) * 0.9, + ]; + + final double animationValue; + final double minRadius; + final double maxRadius; + final int minAlpha; + final int maxAlpha; + final double spacing; + final Color ballColor; + final List alphaList; + final List radiusList; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..strokeJoin = StrokeJoin.round + ..strokeCap = StrokeCap.round; + + progress += (lastExtent - animationValue).abs(); + lastExtent = animationValue; + if (progress >= double.maxFinite) { + progress = .0; + lastExtent = .0; + } + + var diffAlpha = maxAlpha - minAlpha; + var diffRadius = maxRadius - minRadius; + for (int i = 0; i < radiusList.length; i++) { + var offsetAlpha = asin((alphaList[i] - minAlpha) / diffAlpha); + var beatAlpha = + sin(progress * pi / 180 + offsetAlpha).abs() * diffAlpha + minAlpha; + paint.color = Color.fromARGB( + beatAlpha.round(), ballColor.red, ballColor.green, ballColor.blue); + + var dx = maxRadius + 2 * i * maxRadius + i * spacing; + var offset = Offset(dx, maxRadius); + var offsetExtent = asin((radiusList[i] - minRadius) / diffRadius); + var scaleRadius = + sin(progress * pi / 180 + offsetExtent).abs() * diffRadius + + minRadius; + canvas.drawCircle(offset, scaleRadius, paint); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_clip_rotate_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_clip_rotate_indicator.dart new file mode 100644 index 0000000..a862b57 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_clip_rotate_indicator.dart @@ -0,0 +1,144 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-04 +/// + +class BallClipRotateIndicator extends StatefulWidget { + BallClipRotateIndicator({ + this.startAngle: -5, + this.minRadius: 12, + this.maxRadius: 20, + this.color: Colors.white, + this.duration: const Duration(milliseconds: 400), + }); + + final double startAngle; + final double minRadius; + final double maxRadius; + final Color color; + final Duration duration; + + @override + State createState() => _BallClipRotateIndicatorState(); +} + +class _BallClipRotateIndicatorState extends State + with SingleTickerProviderStateMixin { + late Animation _radius; + late Animation _rotate; + late AnimationController _controller; + + @override + void initState() { + _controller = AnimationController(duration: widget.duration, vsync: this); + _radius = + Tween(begin: widget.minRadius, end: widget.maxRadius).animate( + CurvedAnimation( + parent: _controller, + curve: Interval(0, 1, curve: Curves.fastOutSlowIn), + ), + ); + _rotate = Tween( + begin: 0, + end: 360, + ).animate( + CurvedAnimation( + parent: _controller, + curve: Interval(0, 1, curve: Curves.fastOutSlowIn), + ), + ); + _controller.addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + _controller.reverse(); + } else if (status == AnimationStatus.dismissed) { + _controller.forward(); + } + }); + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Size _measureSize() { + return Size(2 * widget.maxRadius, 2 * widget.maxRadius); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return CustomPaint( + size: _measureSize(), + painter: _BallClipRotateIndicatorPainter( + angle: _rotate.value, + radius: _radius.value, + startAngle: widget.startAngle, + minRadius: widget.minRadius, + maxRadius: widget.maxRadius, + color: widget.color, + ), + ); + }, + ); + } +} + +double _progress = .0; +double _lastExtent = .0; + +class _BallClipRotateIndicatorPainter extends CustomPainter { + _BallClipRotateIndicatorPainter({ + required this.angle, + required this.radius, + required this.minRadius, + required this.maxRadius, + required this.startAngle, + required this.color, + }); + + final double angle; + final double radius; + final double minRadius; + final double maxRadius; + final double startAngle; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke + ..strokeWidth = 2 + ..strokeCap = StrokeCap.round + ..color = color; + + _progress += (_lastExtent - angle).abs(); + _lastExtent = angle; + if (_progress >= double.maxFinite) { + _progress = .0; + _lastExtent = .0; + } + + canvas.translate(size.width * .5, size.height * .5); + canvas.rotate((_progress + startAngle) * pi / 180); + var preScale = minRadius / maxRadius; + var scale = preScale + (radius - minRadius) / maxRadius; + canvas.scale(scale); + Rect rect = Rect.fromLTWH( + -size.width * .5, -size.height * .5, size.width, size.height); + canvas.drawArc(rect, -45 * pi / 180, 270 * pi / 180, false, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_clip_rotate_multiple_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_clip_rotate_multiple_indicator.dart new file mode 100644 index 0000000..b44048f --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_clip_rotate_multiple_indicator.dart @@ -0,0 +1,175 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-05 +/// + +class BallClipRotateMultipleIndicator extends StatefulWidget { + BallClipRotateMultipleIndicator({ + this.startAngle: 0, + this.minRadius: 10, + this.maxRadius: 20, + this.dashCircleRadius: 10, + this.color: Colors.white, + this.duration: const Duration(milliseconds: 400), + }); + + final double startAngle; + final double minRadius; + final double maxRadius; + final double dashCircleRadius; + final Color color; + final Duration duration; + + @override + State createState() => _BallClipRotateMultipleIndicator(); +} + +class _BallClipRotateMultipleIndicator + extends State + with SingleTickerProviderStateMixin { + late Animation _radius; + late Animation _rotate; + late AnimationController _controller; + + @override + void initState() { + _controller = AnimationController(duration: widget.duration, vsync: this); + _radius = + Tween(begin: widget.minRadius, end: widget.maxRadius).animate( + CurvedAnimation( + parent: _controller, + curve: Interval(0, 1, curve: Curves.fastOutSlowIn), + ), + ); + _rotate = Tween( + begin: 0, + end: 180, + ).animate( + CurvedAnimation( + parent: _controller, + curve: Interval(0, 1, curve: Curves.fastOutSlowIn), + ), + ); + _controller.addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + _controller.reverse(); + } else if (status == AnimationStatus.dismissed) { + _controller.forward(); + } + }); + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Size _measureSize() { + return Size(2 * widget.maxRadius, 2 * widget.maxRadius); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return CustomPaint( + size: _measureSize(), + painter: _BallClipRotateMultipleIndicatorPainter( + angle: _rotate.value, + radius: _radius.value, + minRadius: widget.minRadius, + maxRadius: widget.maxRadius, + dashCircleRadius: widget.dashCircleRadius, + startAngle: widget.startAngle, + color: widget.color, + ), + ); + }, + ); + } +} + +double _progress = .0; +double _lastExtent = .0; + +class _BallClipRotateMultipleIndicatorPainter extends CustomPainter { + _BallClipRotateMultipleIndicatorPainter({ + required this.angle, + required this.radius, + required this.minRadius, + required this.maxRadius, + required this.dashCircleRadius, + required this.startAngle, + required this.color, + }) : outsideStartAngles = [135, -45], + insideStartAngles = [225, 45]; + + final double angle; + final double radius; + final double minRadius; + final double maxRadius; + final double dashCircleRadius; + final double startAngle; + final List outsideStartAngles; + final List insideStartAngles; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round + ..strokeWidth = 2 + ..color = color; + + _progress += (_lastExtent - angle).abs(); + _lastExtent = angle; + if (_progress >= double.maxFinite) { + _progress = .0; + _lastExtent = .0; + } + + var halfWidth = size.width * .5; + var halfHeight = size.height * .5; + + canvas.save(); + canvas.translate(halfWidth, halfHeight); + canvas.rotate((_progress + startAngle) * pi / 180); + var preScale = minRadius / maxRadius; + var scale = preScale + (radius - minRadius) / maxRadius; + canvas.scale(scale); + + for (var i = 0; i < outsideStartAngles.length; i++) { + Rect rect = + Rect.fromLTWH(-halfWidth, -halfHeight, size.width, size.height); + canvas.drawArc( + rect, outsideStartAngles[i] * pi / 180, 90 * pi / 180, false, paint); + } + canvas.restore(); + + canvas.save(); + var dashCircleSize = dashCircleRadius * 2; + canvas.translate(halfWidth, halfHeight); + canvas.rotate((-_progress + startAngle) * pi / 180); + canvas.scale(scale); + for (var i = 0; i < insideStartAngles.length; i++) { + Rect rect = Rect.fromLTWH( + -dashCircleRadius, -dashCircleRadius, dashCircleSize, dashCircleSize); + canvas.drawArc( + rect, insideStartAngles[i] * pi / 180, 90 * pi / 180, false, paint); + } + canvas.restore(); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_clip_rotate_pulse_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_clip_rotate_pulse_indicator.dart new file mode 100644 index 0000000..64ab052 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_clip_rotate_pulse_indicator.dart @@ -0,0 +1,161 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-05 +/// + +class BallClipRotatePulseIndicator extends StatefulWidget { + BallClipRotatePulseIndicator({ + this.startAngle: -5, + this.minRadius: 10, + this.maxRadius: 20, + this.solidCircleRadius: 10, + this.color: Colors.white, + this.duration: const Duration(milliseconds: 400), + }); + + final double startAngle; + final double minRadius; + final double maxRadius; + final double solidCircleRadius; + final Color color; + final Duration duration; + + @override + State createState() => _BallClipRotatePulseIndicatorState(); +} + +class _BallClipRotatePulseIndicatorState + extends State + with SingleTickerProviderStateMixin { + late Animation _radius; + late Animation _rotate; + late AnimationController _controller; + + @override + void initState() { + _controller = AnimationController(duration: widget.duration, vsync: this); + _radius = + Tween(begin: widget.minRadius, end: widget.maxRadius).animate( + CurvedAnimation( + parent: _controller, + curve: Interval(0, 1, curve: Curves.fastOutSlowIn), + ), + ); + _rotate = Tween( + begin: 0, + end: 180, + ).animate( + CurvedAnimation( + parent: _controller, + curve: Interval(0, 1, curve: Curves.fastOutSlowIn), + ), + ); + _controller.addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + _controller.reverse(); + } else if (status == AnimationStatus.dismissed) { + _controller.forward(); + } + }); + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Size _measureSize() { + return Size(widget.maxRadius * 2, widget.maxRadius * 2); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return CustomPaint( + size: _measureSize(), + painter: _BallClipRotatePulseIndicatorPainter( + angle: _rotate.value, + radius: _radius.value, + minRadius: widget.minRadius, + maxRadius: widget.maxRadius, + solidCircleRadius: widget.solidCircleRadius, + startAngle: widget.startAngle, + color: widget.color, + ), + ); + }, + ); + } +} + +double _progress = .0; +double _lastExtent = .0; + +class _BallClipRotatePulseIndicatorPainter extends CustomPainter { + _BallClipRotatePulseIndicatorPainter({ + required this.angle, + required this.radius, + required this.minRadius, + required this.maxRadius, + required this.solidCircleRadius, + required this.startAngle, + required this.color, + }) : startAngles = [225, 45]; + + final double angle; + final double radius; + final double minRadius; + final double maxRadius; + final double solidCircleRadius; + final double startAngle; + final List startAngles; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..color = color + ..strokeWidth = 2 + ..strokeCap = StrokeCap.round; + + _progress += (_lastExtent - angle).abs(); + _lastExtent = angle; + if (_progress >= double.maxFinite) { + _progress = .0; + _lastExtent = .0; + } + + var halfWidth = size.width * .5; + var halfHeight = size.height * .5; + + canvas.translate(halfWidth, halfHeight); + canvas.rotate((_progress + startAngle) * pi / 180); + var preScale = minRadius / maxRadius; + var scale = preScale + (radius - minRadius) / maxRadius; + canvas.scale(scale); + + paint.style = PaintingStyle.stroke; + for (var i = 0; i < startAngles.length; i++) { + Rect rect = + Rect.fromLTWH(-halfWidth, -halfHeight, size.width, size.height); + canvas.drawArc( + rect, startAngles[i] * pi / 180, 90 * pi / 180, false, paint); + } + + paint.style = PaintingStyle.fill; + canvas.drawCircle(Offset(0, 0), solidCircleRadius, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_grid_beat_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_grid_beat_indicator.dart new file mode 100644 index 0000000..614b98a --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_grid_beat_indicator.dart @@ -0,0 +1,142 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:loading_indicator_view_plus/src/infinite_progress.dart'; + +/// +/// author: Vans Z +/// date: 2019-05-31 +/// + +class BallGridBeatIndicator extends StatefulWidget { + BallGridBeatIndicator({ + this.minAlpha: 51, + this.maxAlpha: 255, + this.radius: 7.2, + this.spacing: 3, + this.ballColor: Colors.white, + this.duration: const Duration(milliseconds: 400), + }); + + final int minAlpha; + final int maxAlpha; + final double radius; + final double spacing; + final Color ballColor; + final Duration duration; + + @override + State createState() => _BallGridBeatIndicatorState(); +} + +class _BallGridBeatIndicatorState extends State + with SingleTickerProviderStateMixin, InfiniteProgressMixin { + @override + void initState() { + startEngine(this, widget.duration); + super.initState(); + } + + @override + void dispose() { + closeEngine(); + super.dispose(); + } + + @override + Size measureSize() { + var size = widget.radius * 2 * 3 + widget.spacing * 2; + return Size(size, size); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + return CustomPaint( + size: measureSize(), + painter: _BallGridBeatIndicatorPainter( + animationValue: animationValue, + radius: widget.radius, + minAlpha: widget.minAlpha, + maxAlpha: widget.maxAlpha, + spacing: widget.spacing, + ballColor: widget.ballColor, + ), + ); + }, + ); + } +} + +double _progress = .0; +double _lastExtent = .0; + +class _BallGridBeatIndicatorPainter extends CustomPainter { + _BallGridBeatIndicatorPainter({ + required this.animationValue, + required this.radius, + required this.minAlpha, + required this.maxAlpha, + required this.spacing, + required this.ballColor, + }) : alphaList = [ + minAlpha + (maxAlpha - minAlpha) * 0.9, + minAlpha + (maxAlpha - minAlpha) * 0.8, + minAlpha + (maxAlpha - minAlpha) * 0.7, + minAlpha + (maxAlpha - minAlpha) * 0.6, + minAlpha + (maxAlpha - minAlpha) * 0.5, + minAlpha + (maxAlpha - minAlpha) * 0.4, + minAlpha + (maxAlpha - minAlpha) * 0.3, + minAlpha + (maxAlpha - minAlpha) * 0.2, + minAlpha + (maxAlpha - minAlpha) * 0.1, + ]; + + final double animationValue; + final double radius; + final int minAlpha; + final int maxAlpha; + final double spacing; + final Color ballColor; + final List alphaList; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..strokeJoin = StrokeJoin.round + ..strokeCap = StrokeCap.round; + + _progress += (_lastExtent - animationValue).abs(); + _lastExtent = animationValue; + if (_progress >= double.maxFinite) { + _progress = .0; + _lastExtent = .0; + } + + var diffAlpha = maxAlpha - minAlpha; + for (int i = 0; i < alphaList.length; i++) { + canvas.save(); + int row = i ~/ 3; + int column = i % 3; + + var dx = radius + 2 * column * radius + column * spacing; + var dy = (2 * row + 1) * radius + row * spacing; + var offset = Offset(dx, dy); + + var offsetAlpha = asin((alphaList[i] - minAlpha) / diffAlpha); + var beatAlpha = + sin(_progress * pi / 180 + offsetAlpha).abs() * diffAlpha + minAlpha; + paint.color = Color.fromARGB( + beatAlpha.round(), ballColor.red, ballColor.green, ballColor.blue); + + canvas.drawCircle(offset, radius, paint); + canvas.restore(); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_grid_pulse_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_grid_pulse_indicator.dart new file mode 100644 index 0000000..69a4591 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_grid_pulse_indicator.dart @@ -0,0 +1,162 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:loading_indicator_view_plus/src/infinite_progress.dart'; + +/// +/// author: Vans Z +/// date: 2019-05-31 +/// + +class BallGridPulseIndicator extends StatefulWidget { + BallGridPulseIndicator({ + this.minRadius: 3.6, + this.maxRadius: 7.2, + this.minAlpha: 51, + this.maxAlpha: 255, + this.spacing: 3, + this.ballColor: Colors.white, + this.duration: const Duration(milliseconds: 400), + }); + + final double minRadius; + final double maxRadius; + final double minAlpha; + final double maxAlpha; + final double spacing; + final Color ballColor; + final Duration duration; + + @override + State createState() => _BallGridPulseIndicatorState(); +} + +class _BallGridPulseIndicatorState extends State + with SingleTickerProviderStateMixin, InfiniteProgressMixin { + @override + void initState() { + startEngine(this, widget.duration); + super.initState(); + } + + @override + void dispose() { + closeEngine(); + super.dispose(); + } + + @override + Size measureSize() { + var size = widget.maxRadius * 2 * 3 + widget.spacing * 2; + return Size(size, size); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + return CustomPaint( + size: measureSize(), + painter: _BallGridPulseIndicatorPainter( + animationValue: animationValue, + minRadius: widget.minRadius, + maxRadius: widget.maxRadius, + minAlpha: widget.minAlpha, + maxAlpha: widget.maxAlpha, + spacing: widget.spacing, + ballColor: widget.ballColor), + ); + }); + } +} + +double _progress = .0; +double _lastExtent = .0; + +class _BallGridPulseIndicatorPainter extends CustomPainter { + _BallGridPulseIndicatorPainter({ + required this.animationValue, + required this.minRadius, + required this.maxRadius, + required this.minAlpha, + required this.maxAlpha, + required this.spacing, + required this.ballColor, + }) : radiusList = [ + minRadius + (maxRadius - minRadius) * 0.9, + minRadius + (maxRadius - minRadius) * 0.8, + minRadius + (maxRadius - minRadius) * 0.7, + minRadius + (maxRadius - minRadius) * 0.6, + minRadius + (maxRadius - minRadius) * 0.5, + minRadius + (maxRadius - minRadius) * 0.4, + minRadius + (maxRadius - minRadius) * 0.3, + minRadius + (maxRadius - minRadius) * 0.2, + minRadius + (maxRadius - minRadius) * 0.1, + ], + alphaList = [ + minAlpha + (maxAlpha - minAlpha) * 0.9, + minAlpha + (maxAlpha - minAlpha) * 0.8, + minAlpha + (maxAlpha - minAlpha) * 0.7, + minAlpha + (maxAlpha - minAlpha) * 0.6, + minAlpha + (maxAlpha - minAlpha) * 0.5, + minAlpha + (maxAlpha - minAlpha) * 0.4, + minAlpha + (maxAlpha - minAlpha) * 0.3, + minAlpha + (maxAlpha - minAlpha) * 0.2, + minAlpha + (maxAlpha - minAlpha) * 0.1, + ]; + final double animationValue; + final double minRadius; + final double maxRadius; + final double minAlpha; + final double maxAlpha; + final double spacing; + final Color ballColor; + final List radiusList; + final List alphaList; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = ballColor + ..strokeJoin = StrokeJoin.round + ..strokeCap = StrokeCap.round; + + _progress += (_lastExtent - animationValue).abs(); + _lastExtent = animationValue; + if (_progress >= double.maxFinite) { + _progress = .0; + _lastExtent = .0; + } + + var diffRadius = maxRadius - minRadius; + var diffAlpha = maxAlpha - minAlpha; + for (int i = 0; i < radiusList.length; i++) { + canvas.save(); + int row = i ~/ 3; + int column = i % 3; + + var dx = maxRadius + 2 * column * maxRadius + column * spacing; + var dy = (2 * row + 1) * maxRadius + row * spacing; + var offset = Offset(dx, dy); + + var offsetAlpha = asin((alphaList[i] - minAlpha) / diffAlpha); + var beatAlpha = + sin(_progress * pi / 180 + offsetAlpha).abs() * diffAlpha + minAlpha; + paint.color = Color.fromARGB( + beatAlpha.round(), ballColor.red, ballColor.green, ballColor.blue); + + var offsetExtent = asin((radiusList[i] - minRadius) / diffRadius); + var scaleRadius = + sin(_progress * pi / 180 + offsetExtent).abs() * diffRadius + + minRadius; + canvas.drawCircle(offset, scaleRadius, paint); + canvas.restore(); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_pulse_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_pulse_indicator.dart new file mode 100644 index 0000000..a6c6ed2 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_pulse_indicator.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'dart:math'; + +import 'package:loading_indicator_view_plus/src/infinite_progress.dart'; + +/// +/// author:Vans Z +/// date: 2019-05-27 +/// +class BallPulseIndicator extends StatefulWidget { + BallPulseIndicator({ + this.minRadius: 2.4, + this.maxRadius: 7.2, + this.spacing: 3, + this.ballColor: Colors.white, + this.duration: const Duration(milliseconds: 400), + }); + + final double minRadius; + final double maxRadius; + final double spacing; + final Color ballColor; + final Duration duration; + + @override + State createState() => _BallPulseIndicatorState(); +} + +class _BallPulseIndicatorState extends State + with SingleTickerProviderStateMixin, InfiniteProgressMixin { + @override + void initState() { + startEngine(this, widget.duration); + super.initState(); + } + + @override + void dispose() { + closeEngine(); + super.dispose(); + } + + @override + Size measureSize() { + var width = widget.maxRadius * 2 * 3 + widget.spacing * 2; + var height = widget.maxRadius * 2; + return Size(width, height); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + return CustomPaint( + size: measureSize(), + painter: _BallPulseIndicatorPainter( + animationValue: animationValue, + minRadius: widget.minRadius, + maxRadius: widget.maxRadius, + spacing: widget.spacing, + ballColor: widget.ballColor, + ), + ); + }, + ); + } +} + +double _progress = .0; +double _lastExtent = .0; + +class _BallPulseIndicatorPainter extends CustomPainter { + _BallPulseIndicatorPainter({ + required this.animationValue, + required this.minRadius, + required this.maxRadius, + required this.spacing, + required this.ballColor, + }) : radiusList = [ + minRadius + (maxRadius - minRadius) * 0.9, + minRadius + (maxRadius - minRadius) * 0.6, + minRadius + (maxRadius - minRadius) * 0.3, + ]; + + final double animationValue; + final double minRadius; + final double maxRadius; + final double spacing; + final Color ballColor; + final List radiusList; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = ballColor + ..strokeJoin = StrokeJoin.round + ..strokeCap = StrokeCap.round; + + _progress += (_lastExtent - animationValue).abs(); + _lastExtent = animationValue; + if (_progress >= double.maxFinite) { + _progress = .0; + _lastExtent = .0; + } + + var diffRadius = maxRadius - minRadius; + for (int i = 0; i < radiusList.length; i++) { + var dx = maxRadius + 2 * i * maxRadius + i * spacing; + var offset = Offset(dx, maxRadius); + + var offsetExtent = asin((radiusList[i] - minRadius) / diffRadius); + var scaleRadius = + sin(_progress * pi / 180 + offsetExtent).abs() * diffRadius + + minRadius; + canvas.drawCircle(offset, scaleRadius, paint); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_pulse_rise_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_pulse_rise_indicator.dart new file mode 100644 index 0000000..5e2ede9 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_pulse_rise_indicator.dart @@ -0,0 +1,129 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-07 +/// + +class BallPulseRiseIndicator extends StatefulWidget { + BallPulseRiseIndicator({ + this.ballRadius: 4.8, + this.horizontalSpacing: 12, + this.verticalSpacing: 24, + this.color: Colors.white, + this.duration: const Duration(milliseconds: 1250), + }); + + final double ballRadius; + final double horizontalSpacing; + final double verticalSpacing; + final Color color; + final Duration duration; + + @override + State createState() => _BallPulseRiseIndicatorState(); +} + +class _BallPulseRiseIndicatorState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _rotateX; + + @override + void initState() { + _controller = AnimationController(vsync: this, duration: widget.duration); + _rotateX = Tween(begin: 0, end: 360) + .animate(CurvedAnimation(parent: _controller, curve: Curves.linear)); + _controller.addListener(() { + if (_controller.isCompleted) { + _controller.reset(); + _controller.forward(); + } + }); + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return CustomPaint( + size: _measureSize(), + painter: _BallPulseRiseIndicatorPainter( + rotateX: _rotateX.value, + ballRadius: widget.ballRadius, + horizontalSpacing: widget.horizontalSpacing, + verticalSpacing: widget.verticalSpacing, + color: widget.color, + ), + ); + }, + ); + } + + Size _measureSize() { + var width = widget.ballRadius * 2 * 3 + widget.horizontalSpacing * 2; + var height = widget.ballRadius * 2 * 2 + widget.verticalSpacing; + return Size(width, height); + } +} + +class _BallPulseRiseIndicatorPainter extends CustomPainter { + _BallPulseRiseIndicatorPainter({ + required this.rotateX, + required this.ballRadius, + required this.horizontalSpacing, + required this.verticalSpacing, + required this.color, + }); + + final double rotateX; + final double ballRadius; + final double horizontalSpacing; + final double verticalSpacing; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = color; + + var matrix = Matrix4.rotationX(rotateX * pi / 180); + canvas.translate(0, size.height * .5); + canvas.transform(matrix.storage); + + var dx, dy; + + dx = (size.width - 2 * ballRadius - horizontalSpacing) * .5; + dy = (-verticalSpacing - ballRadius) * .5; + canvas.drawCircle(Offset(dx, dy), ballRadius, paint); + + dx = size.width - dx; + canvas.drawCircle(Offset(dx, dy), ballRadius, paint); + + dx = ballRadius; + dy = verticalSpacing * .5 + ballRadius; + canvas.drawCircle(Offset(dx, dy), ballRadius, paint); + + dx = 3 * ballRadius + horizontalSpacing; + canvas.drawCircle(Offset(dx, dy), ballRadius, paint); + + dx = 5 * ballRadius + 2 * horizontalSpacing; + canvas.drawCircle(Offset(dx, dy), ballRadius, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_pulse_sync_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_pulse_sync_indicator.dart new file mode 100644 index 0000000..8697016 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_pulse_sync_indicator.dart @@ -0,0 +1,116 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:loading_indicator_view_plus/src/infinite_progress.dart'; + +/// +/// author:Vans Z +/// date: 2019-05-29 +/// + +class BallPulseSyncIndicator extends StatefulWidget { + BallPulseSyncIndicator({ + this.radius: 7.2, + this.extent: 16, + this.spacing: 3, + this.ballColor: Colors.white, + this.duration: const Duration(milliseconds: 400), + }); + + final double radius; + final double extent; + final double spacing; + final Color ballColor; + final Duration duration; + + @override + State createState() => _BallPulseSyncIndicatorState(); +} + +class _BallPulseSyncIndicatorState extends State + with TickerProviderStateMixin, InfiniteProgressMixin { + @override + void initState() { + startEngine(this, widget.duration); + super.initState(); + } + + @override + void dispose() { + closeEngine(); + super.dispose(); + } + + @override + Size measureSize() { + var width = widget.radius * 2 * 3 + widget.spacing * 2; + var height = widget.extent + widget.radius * 2; + return Size(width, height); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + return CustomPaint( + size: measureSize(), + painter: _BallPulseSyncIndicatorPainter( + animationValue: animationValue, + extent: widget.extent, + radius: widget.radius, + spacing: widget.spacing, + ballColor: widget.ballColor, + ), + ); + }); + } +} + +double _progress = .0; +double _lastExtent = .0; + +class _BallPulseSyncIndicatorPainter extends CustomPainter { + _BallPulseSyncIndicatorPainter({ + required this.animationValue, + required this.extent, + required this.radius, + required this.spacing, + required this.ballColor, + }) : extentList = [extent * 0.9, extent * 0.6, extent * 0.3]; + + final double animationValue; + final double extent; + final double radius; + final double spacing; + final Color ballColor; + final List extentList; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = ballColor + ..strokeJoin = StrokeJoin.round + ..strokeCap = StrokeCap.round; + + _progress += (_lastExtent - animationValue).abs(); + _lastExtent = animationValue; + if (_progress >= double.maxFinite) { + _progress = .0; + _lastExtent = .0; + } + for (int i = 0; i < extentList.length; i++) { + var dx = radius + 2 * i * radius + i * spacing; + var offsetExtent = asin(extentList[i] / extent); + var offsetY = + sin(_progress * pi / 180 + offsetExtent).abs() * extent + radius; + var offset = Offset(dx, offsetY); + canvas.drawCircle(offset, radius, paint); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_rotate_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_rotate_indicator.dart new file mode 100644 index 0000000..52a2aea --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_rotate_indicator.dart @@ -0,0 +1,135 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-07 +/// + +class BallRotateIndicator extends StatefulWidget { + BallRotateIndicator({ + this.minBallRadius: 2, + this.maxBallRadius: 4, + this.spacing: 7, + this.color: Colors.white, + this.duration: const Duration(milliseconds: 500), + }); + + final double minBallRadius; + final double maxBallRadius; + final double spacing; + final Color color; + final Duration duration; + + @override + State createState() => _BallRotateIndicatorState(); +} + +class _BallRotateIndicatorState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _radius; + late Animation _rotate; + + @override + void initState() { + _controller = AnimationController(duration: widget.duration, vsync: this); + _radius = + Tween(begin: widget.minBallRadius, end: widget.maxBallRadius) + .animate( + CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn), + ); + _rotate = Tween(begin: 0, end: 180).animate( + CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn), + ); + _controller.addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + _controller.reverse(); + } else if (status == AnimationStatus.dismissed) { + _controller.forward(); + } + }); + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Size measureSize() { + var width = widget.maxBallRadius * 2 * 3 + widget.spacing * 2; + var height = widget.maxBallRadius * 2; + return Size(width, height); + } + + @override + Widget build(BuildContext context) => AnimatedBuilder( + animation: _controller, + builder: (context, child) => CustomPaint( + size: measureSize(), + painter: _BallRotateIndicatorPainter( + angle: _rotate.value, + radius: _radius.value, + maxBallRadius: widget.maxBallRadius, + minBallRadius: widget.minBallRadius, + spacing: widget.spacing, + color: widget.color, + ), + ), + ); +} + +double _progress = .0; +double _lastExtent = .0; + +class _BallRotateIndicatorPainter extends CustomPainter { + _BallRotateIndicatorPainter({ + required this.angle, + required this.radius, + required this.minBallRadius, + required this.maxBallRadius, + required this.spacing, + required this.color, + }); + + final double angle; + final double radius; + final double minBallRadius; + final double maxBallRadius; + final double spacing; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = color; + + _progress += (_lastExtent - angle).abs(); + _lastExtent = angle; + if (_progress >= double.maxFinite) { + _progress = .0; + _lastExtent = .0; + } + + canvas.translate(size.width * .5, size.height * .5); + canvas.rotate((_progress) * pi / 180); + + var preScale = minBallRadius / maxBallRadius; + var scale = preScale + (radius - minBallRadius) / maxBallRadius; + canvas.scale(scale); + + for (var i = 0; i < 3; i++) { + var dx = (2 * i - 2) * maxBallRadius + (i - 1) * spacing; + canvas.drawCircle(Offset(dx, 0), maxBallRadius, paint); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_scale_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_scale_indicator.dart new file mode 100644 index 0000000..7cb24fe --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_scale_indicator.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-01 +/// + +class BallScaleIndicator extends StatefulWidget { + BallScaleIndicator({ + this.radius: 20, + this.ballColor: Colors.white, + this.duration: const Duration(milliseconds: 1000), + }); + + final double radius; + final Color ballColor; + final Duration duration; + + @override + State createState() => _BallScaleIndicatorState(); +} + +class _BallScaleIndicatorState extends State + with SingleTickerProviderStateMixin { + late Animation _animation; + late AnimationController _controller; + + @override + void initState() { + _controller = AnimationController(vsync: this, duration: widget.duration); + _animation = CurvedAnimation(parent: _controller, curve: Curves.linear); + _animation = Tween(begin: 0, end: widget.radius).animate(_animation) + ..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + _controller.reset(); + _controller.forward(); + } + }); + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Size _measureSize() { + var size = 2 * widget.radius; + return Size(size, size); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return CustomPaint( + size: _measureSize(), + painter: _BallScaleIndicatorPainter( + animationValue: _animation.value, + radius: widget.radius, + ballColor: widget.ballColor, + ), + ); + }, + ); + } +} + +class _BallScaleIndicatorPainter extends CustomPainter { + _BallScaleIndicatorPainter({ + required this.animationValue, + required this.radius, + required this.ballColor, + }); + + final double animationValue; + final double radius; + final Color ballColor; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..color = Color.fromARGB((255 * (1 - animationValue / radius)).toInt(), + ballColor.red, ballColor.green, ballColor.blue) + ..style = PaintingStyle.fill; + + var center = Offset(radius, radius); + canvas.drawCircle(center, animationValue, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_scale_multiple_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_scale_multiple_indicator.dart new file mode 100644 index 0000000..44c4e28 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_scale_multiple_indicator.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-01 +/// + +class BallScaleMultipleIndicator extends StatefulWidget { + BallScaleMultipleIndicator({ + this.radius: 20, + this.ballColor: Colors.white, + this.duration: const Duration(milliseconds: 1000), + }); + + final double radius; + final Color ballColor; + final Duration duration; + + @override + State createState() => _BallScaleMultipleIndicatorState(); +} + +class _BallScaleMultipleIndicatorState extends State + with SingleTickerProviderStateMixin { + late Animation _animation; + late AnimationController _controller; + + @override + void initState() { + _controller = AnimationController(vsync: this, duration: widget.duration); + _animation = CurvedAnimation(parent: _controller, curve: Curves.linear); + _animation = Tween(begin: 0, end: widget.radius).animate(_animation) + ..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + _controller.reset(); + _controller.forward(); + } + }); + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Size _measureSize() { + var size = 2 * widget.radius; + return Size(size, size); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return CustomPaint( + size: _measureSize(), + painter: _BallScaleMultipleIndicatorPainter( + animationValue: _animation.value, + radius: widget.radius, + ballColor: widget.ballColor, + duration: widget.duration, + ), + ); + }, + ); + } +} + +class _BallScaleMultipleIndicatorPainter extends CustomPainter { + _BallScaleMultipleIndicatorPainter({ + required this.animationValue, + required this.radius, + required this.ballColor, + required this.duration, + }) : offsetList = [radius, radius * .7, radius * .4]; + + final double animationValue; + final double radius; + final Color ballColor; + final Duration duration; + final List offsetList; + + @override + void paint(Canvas canvas, Size size) { + var percent = animationValue / radius; + var paint = Paint() + ..isAntiAlias = true + ..color = Color.fromARGB((255 * (1 - percent)).toInt(), ballColor.red, + ballColor.green, ballColor.blue) + ..style = PaintingStyle.fill; + + var center = Offset(radius, radius); + + for (var i = 0; i < offsetList.length; i++) { + canvas.save(); + canvas.drawCircle(center, offsetList[i] * percent, paint); + canvas.restore(); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_scale_ripple_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_scale_ripple_indicator.dart new file mode 100644 index 0000000..032a04f --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_scale_ripple_indicator.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-03 +/// + +class BallScaleRippleIndicator extends StatefulWidget { + BallScaleRippleIndicator({ + this.radius: 20, + this.ballColor: Colors.white, + this.duration: const Duration(milliseconds: 1000), + }); + + final double radius; + final Color ballColor; + final Duration duration; + + @override + State createState() => _BallScaleRippleIndicatorState(); +} + +class _BallScaleRippleIndicatorState extends State + with SingleTickerProviderStateMixin { + late Animation _radius; + late Animation _alpha; + late AnimationController _controller; + + @override + void initState() { + _controller = AnimationController(vsync: this, duration: widget.duration); + _radius = Tween(begin: 0, end: widget.radius) + .animate(CurvedAnimation(parent: _controller, curve: Interval(0, 0.6))); + _alpha = Tween(begin: 255, end: 0) + .animate(CurvedAnimation(parent: _controller, curve: Interval(0.6, 1))); + _controller.addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + _controller.reset(); + _controller.forward(); + } + }); + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Size _measureSize() { + var size = 2 * widget.radius; + return Size(size, size); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return CustomPaint( + size: _measureSize(), + painter: _BallScaleRippleIndicatorPainter( + radiusAnimValue: _radius.value, + alphaAnimValue: _alpha.value, + totalRadius: widget.radius, + ballColor: widget.ballColor, + ), + ); + }, + ); + } +} + +class _BallScaleRippleIndicatorPainter extends CustomPainter { + _BallScaleRippleIndicatorPainter({ + required this.radiusAnimValue, + required this.alphaAnimValue, + required this.totalRadius, + required this.ballColor, + }); + + final double radiusAnimValue; + final double alphaAnimValue; + final double totalRadius; + final Color ballColor; + + @override + void paint(Canvas canvas, Size size) { + var percent = radiusAnimValue / totalRadius; + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke + ..strokeWidth = 1.2 * percent; + + if (alphaAnimValue >= 255) { + paint.color = Color.fromARGB((255 * percent).round(), ballColor.red, + ballColor.green, ballColor.blue); + } else { + paint.color = Color.fromARGB(alphaAnimValue.round(), ballColor.red, + ballColor.green, ballColor.blue); + } + + var center = Offset(totalRadius, totalRadius); + canvas.drawCircle(center, radiusAnimValue, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_scale_ripple_multiple_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_scale_ripple_multiple_indicator.dart new file mode 100644 index 0000000..1ed92f3 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_scale_ripple_multiple_indicator.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-01 +/// + +class BallScaleRippleMultipleIndicator extends StatefulWidget { + BallScaleRippleMultipleIndicator({ + this.radius: 20, + this.ballColor: Colors.white, + this.duration: const Duration(milliseconds: 1000), + }); + + final double radius; + final Color ballColor; + final Duration duration; + + @override + State createState() => + _BallScaleRippleMultipleIndicatorState(); +} + +class _BallScaleRippleMultipleIndicatorState + extends State + with SingleTickerProviderStateMixin { + late Animation _animation; + late AnimationController _controller; + + @override + void initState() { + _controller = AnimationController(vsync: this, duration: widget.duration); + _animation = CurvedAnimation(parent: _controller, curve: Curves.linear); + _animation = Tween(begin: 0, end: widget.radius).animate(_animation) + ..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + _controller.reset(); + _controller.forward(); + } + }); + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Size _measureSize() { + var size = 2 * widget.radius; + return Size(size, size); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return CustomPaint( + size: _measureSize(), + painter: _BallScaleRippleMultipleIndicatorPainter( + animationValue: _animation.value, + radius: widget.radius, + ballColor: widget.ballColor, + duration: widget.duration, + ), + ); + }, + ); + } +} + +class _BallScaleRippleMultipleIndicatorPainter extends CustomPainter { + _BallScaleRippleMultipleIndicatorPainter({ + required this.animationValue, + required this.radius, + required this.ballColor, + required this.duration, + }) : offsetList = [radius, radius * .7, radius * .4], + strokeList = [1.2, .84, 0.48]; + + final double animationValue; + final double radius; + final Color ballColor; + final Duration duration; + final List offsetList; + final List strokeList; + + @override + void paint(Canvas canvas, Size size) { + var percent = animationValue / radius; + var paint = Paint() + ..isAntiAlias = true + ..color = Color.fromARGB((255 * percent).round(), ballColor.red, + ballColor.green, ballColor.blue) + ..style = PaintingStyle.stroke; + + var center = Offset(radius, radius); + + for (var i = 0; i < offsetList.length; i++) { + canvas.save(); + paint.strokeWidth = strokeList[i] * percent; + canvas.drawCircle(center, offsetList[i] * percent, paint); + canvas.restore(); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_spin_fade_loader_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_spin_fade_loader_indicator.dart new file mode 100644 index 0000000..01afc5e --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_spin_fade_loader_indicator.dart @@ -0,0 +1,139 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../infinite_progress.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-02 +/// + +class BallSpinFadeLoaderIndicator extends StatefulWidget { + BallSpinFadeLoaderIndicator({ + this.radius = 24, + this.minBallRadius: 1.6, + this.maxBallRadius: 5, + this.minBallAlpha: 77, + this.maxBallAlpha: 255, + this.ballColor: Colors.white, + this.duration: const Duration(milliseconds: 500), + }); + + final double radius; + final double minBallRadius; + final double maxBallRadius; + final double minBallAlpha; + final double maxBallAlpha; + final Color ballColor; + final Duration duration; + + @override + State createState() => _BallSpinFadeLoaderIndicatorState(); +} + +class _BallSpinFadeLoaderIndicatorState + extends State + with SingleTickerProviderStateMixin, InfiniteProgressMixin { + @override + void initState() { + startEngine(this, widget.duration); + super.initState(); + } + + @override + void dispose() { + closeEngine(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + return CustomPaint( + size: _measureSize(), + painter: _BallSpinFadeLoaderIndicatorPainter( + animationValue: animationValue, + minRadius: widget.minBallRadius, + maxRadius: widget.maxBallRadius, + minAlpha: widget.minBallAlpha, + maxAlpha: widget.maxBallAlpha, + ballColor: widget.ballColor), + ); + }, + ); + } + + Size _measureSize() { + return Size(2 * widget.radius, 2 * widget.radius); + } + + @override + Size measureSize() { + return Size(2 * widget.radius, 2 * widget.radius); + } +} + +double _progress = .0; +double _lastExtent = .0; + +class _BallSpinFadeLoaderIndicatorPainter extends CustomPainter { + _BallSpinFadeLoaderIndicatorPainter({ + required this.animationValue, + required this.minRadius, + required this.maxRadius, + required this.minAlpha, + required this.maxAlpha, + required this.ballColor, + }); + + final double animationValue; + final double minRadius; + final double maxRadius; + final double minAlpha; + final double maxAlpha; + final Color ballColor; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill; + + _progress += (_lastExtent - animationValue).abs(); + _lastExtent = animationValue; + if (_progress >= double.maxFinite) { + _progress = .0; + _lastExtent = .0; + } + + var diffAlpha = maxAlpha - minAlpha; + var diffRadius = maxRadius - minRadius; + for (int i = 0; i < 8; i++) { + canvas.save(); + + var newProgress = _progress - i * 22.5; + var beatAlpha = sin(newProgress * pi / 180).abs() * diffAlpha + minAlpha; + paint.color = Color.fromARGB( + beatAlpha.round(), ballColor.red, ballColor.green, ballColor.blue); + var scaleRadius = + sin(newProgress * pi / 180).abs() * diffRadius + minRadius; + var point = _circleAt(size.width * .5, size.height * .5, + size.width * .5 - maxRadius, i * pi / 4); + canvas.drawCircle(point, scaleRadius, paint); + + canvas.restore(); + } + } + + Offset _circleAt(double width, double height, double radius, double angle) { + var x = width + radius * (cos(angle)); + var y = height + radius * (sin(angle)); + return Offset(x, y); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_zig_zag_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_zig_zag_indicator.dart new file mode 100644 index 0000000..74a4216 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/ball_zig_zag_indicator.dart @@ -0,0 +1,172 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-06 +/// + +class BallZigZagIndicator extends StatefulWidget { + BallZigZagIndicator({ + this.width: 40, + this.height: 40, + this.ballRadius: 4.8, + this.color: Colors.white, + this.duration: const Duration(milliseconds: 1000), + }); + + final double width; + final double height; + final double ballRadius; + final Color color; + final Duration duration; + + @override + State createState() => _BallZigZagIndicatorState(); +} + +class _BallZigZagIndicatorState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _horizontal; + late Animation _rightWaistX; + late Animation _rightWaistY; + late Animation _leftWaistX; + late Animation _leftWaistY; + + @override + void initState() { + _controller = AnimationController(vsync: this, duration: widget.duration); + + var waistLength = sqrt(pow(widget.width * .5 - widget.ballRadius, 2) + + pow(widget.height * .5 - widget.ballRadius, 2)); + var horizontalLength = widget.width - 2 * widget.ballRadius; + var triangleLength = horizontalLength + 2 * waistLength; + var horEndInterval = horizontalLength / triangleLength; + var rightEndInterval = waistLength / triangleLength + horEndInterval; + var leftEndInterval = waistLength / triangleLength + rightEndInterval; + + var begin = widget.ballRadius; + var end = widget.width - widget.ballRadius; + + _horizontal = Tween(begin: begin, end: end).animate(CurvedAnimation( + parent: _controller, curve: Interval(0, horEndInterval))); + + begin = widget.width - widget.ballRadius; + end = widget.width * .5; + _rightWaistX = Tween(begin: begin, end: end).animate( + CurvedAnimation( + parent: _controller, + curve: Interval(horEndInterval, rightEndInterval))); + begin = widget.ballRadius; + end = widget.height * .5; + _rightWaistY = Tween(begin: begin, end: end).animate( + CurvedAnimation( + parent: _controller, + curve: Interval(horEndInterval, rightEndInterval))); + + begin = widget.width * .5; + end = widget.ballRadius; + _leftWaistX = Tween(begin: begin, end: end).animate(CurvedAnimation( + parent: _controller, + curve: Interval(rightEndInterval, leftEndInterval))); + begin = widget.height * .5; + end = widget.ballRadius; + _leftWaistY = Tween(begin: begin, end: end).animate(CurvedAnimation( + parent: _controller, + curve: Interval(rightEndInterval, leftEndInterval))); + + _controller.addListener(() { + if (_controller.isCompleted) { + _controller.reset(); + _controller.forward(); + } + }); + _controller.forward(); + + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, child) => CustomPaint( + size: Size(widget.width, widget.height), + painter: _BallZigZagIndicatorPainter( + horizontal: _horizontal.value, + rightWaistX: _rightWaistX.value, + rightWaistY: _rightWaistY.value, + leftWaistX: _leftWaistX.value, + leftWaistY: _leftWaistY.value, + ballRadius: widget.ballRadius, + color: widget.color, + ), + ), + ); + } +} + +class _BallZigZagIndicatorPainter extends CustomPainter { + _BallZigZagIndicatorPainter({ + required this.horizontal, + required this.rightWaistX, + required this.rightWaistY, + required this.leftWaistX, + required this.leftWaistY, + required this.ballRadius, + required this.color, + }); + + final double horizontal; + final double rightWaistX; + final double rightWaistY; + final double leftWaistX; + final double leftWaistY; + final double ballRadius; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = color; + + canvas.save(); + if (horizontal < size.width - ballRadius) { + canvas.drawCircle(Offset(horizontal, ballRadius), ballRadius, paint); + } else if (rightWaistX > size.width * .5 && + rightWaistY < size.height * .5) { + canvas.drawCircle(Offset(rightWaistX, rightWaistY), ballRadius, paint); + } else if (leftWaistX > ballRadius && leftWaistY > ballRadius) { + canvas.drawCircle(Offset(leftWaistX, leftWaistY), ballRadius, paint); + } + canvas.restore(); + + canvas.save(); + canvas.translate(0, size.height); + canvas.transform(Matrix4.rotationX(pi).storage); + canvas.translate(size.width, 0); + canvas.transform(Matrix4.rotationY(pi).storage); + if (horizontal < size.width - ballRadius) { + canvas.drawCircle(Offset(horizontal, ballRadius), ballRadius, paint); + } else if (rightWaistX > size.width * .5 && + rightWaistY < size.height * .5) { + canvas.drawCircle(Offset(rightWaistX, rightWaistY), ballRadius, paint); + } else if (leftWaistX > ballRadius && leftWaistY > ballRadius) { + canvas.drawCircle(Offset(leftWaistX, leftWaistY), ballRadius, paint); + } + canvas.restore(); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/cube_transition_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/cube_transition_indicator.dart new file mode 100644 index 0000000..db98e93 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/cube_transition_indicator.dart @@ -0,0 +1,196 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-06 +/// + +class CubeTransitionIndicator extends StatefulWidget { + CubeTransitionIndicator({ + this.size: 36, + this.minCubeSize: 4, + this.maxCubeSize: 8, + this.cubeColor: Colors.white, + this.duration: const Duration(milliseconds: 1600), + }); + + final double size; + final double minCubeSize; + final double maxCubeSize; + final Color cubeColor; + final Duration duration; + + @override + State createState() => _CubeTransitionIndicatorState(); +} + +class _CubeTransitionIndicatorState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation transX; + late Animation transReverseX; + late Animation transY; + late Animation transReverseY; + + @override + void initState() { + _controller = AnimationController(vsync: this, duration: widget.duration); + var end = widget.size - widget.maxCubeSize; + transX = Tween(begin: 0, end: end).animate( + CurvedAnimation(parent: _controller, curve: Interval(0, 0.25))); + transY = Tween(begin: 0, end: end).animate( + CurvedAnimation(parent: _controller, curve: Interval(0.25, 0.5))); + transReverseX = Tween(begin: 0, end: end).animate( + CurvedAnimation(parent: _controller, curve: Interval(0.5, 0.75))); + transReverseY = Tween(begin: 0, end: end).animate( + CurvedAnimation(parent: _controller, curve: Interval(0.75, 1))); + + _controller.addListener(() { + if (_controller.isCompleted) { + _controller.reset(); + _controller.forward(); + } + }); + + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return CustomPaint( + size: Size(widget.size, widget.size), + painter: _CubeTransitionIndicatorPainter( + transX: transX.value, + transReverseX: transReverseX.value, + transY: transY.value, + transReverseY: transReverseY.value, + minCubeSize: widget.minCubeSize, + maxCubeSize: widget.maxCubeSize, + color: widget.cubeColor, + ), + ); + }, + ); +} + +class _CubeTransitionIndicatorPainter extends CustomPainter { + _CubeTransitionIndicatorPainter({ + required this.transX, + required this.transReverseX, + required this.transY, + required this.transReverseY, + required this.minCubeSize, + required this.maxCubeSize, + required this.color, + }); + + final double transX; + final double transReverseX; + final double transY; + final double transReverseY; + final double minCubeSize; + final double maxCubeSize; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = color; + + var end = size.width - maxCubeSize; + drawLTCube(end, canvas, paint); + drawRBCube(end, canvas, paint); + } + + void drawRBCube(double end, Canvas canvas, Paint paint) { + var percent, scale, dx, dy; + final radian = pi / 180; + final left = -maxCubeSize * .5; + final top = -maxCubeSize * .5; + + canvas.save(); + + if (transX < end) { + percent = transX / end; + scale = 1 - (1 - minCubeSize / maxCubeSize) * percent; + dx = end + maxCubeSize * .5 - transX; + dy = end + maxCubeSize * .5; + } else if (transY < end) { + percent = transY / end; + scale = (1 - minCubeSize / maxCubeSize) * percent + 0.5; + dx = maxCubeSize * .5; + dy = end + maxCubeSize * .5 - transY; + } else if (transReverseX < end) { + percent = transReverseX / end; + scale = 1 - (1 - minCubeSize / maxCubeSize) * percent; + dx = transReverseX + maxCubeSize * .5; + dy = maxCubeSize * .5; + } else if (transReverseY < end) { + percent = transReverseY / end; + scale = (1 - minCubeSize / maxCubeSize) * percent + 0.5; + dx = end + maxCubeSize * .5; + dy = transReverseY + maxCubeSize * .5; + } + + canvas.translate(dx, dy); + canvas.rotate(-90 * percent * radian); + canvas.scale(scale); + canvas.drawRect(Rect.fromLTWH(left, top, maxCubeSize, maxCubeSize), paint); + + canvas.restore(); + } + + void drawLTCube(double end, Canvas canvas, Paint paint) { + var percent, scale, dx, dy; + final radian = pi / 180; + final left = -maxCubeSize * .5; + final top = -maxCubeSize * .5; + + canvas.save(); + + if (transX < end) { + percent = transX / end; + scale = 1 - (1 - minCubeSize / maxCubeSize) * percent; + dx = transX + maxCubeSize * .5; + dy = maxCubeSize * .5; + } else if (transY < end) { + percent = transY / end; + scale = (1 - minCubeSize / maxCubeSize) * percent + 0.5; + dx = end + maxCubeSize * .5; + dy = transY + maxCubeSize * .5; + } else if (transReverseX < end) { + percent = transReverseX / end; + scale = 1 - (1 - minCubeSize / maxCubeSize) * percent; + dx = end + maxCubeSize * .5 - transReverseX; + dy = end + maxCubeSize * .5; + } else if (transReverseY < end) { + percent = transReverseY / end; + scale = (1 - minCubeSize / maxCubeSize) * percent + 0.5; + dx = maxCubeSize * .5; + dy = end + maxCubeSize * .5 - transReverseY; + } + + canvas.translate(dx, dy); + canvas.rotate(-90 * percent * radian); + canvas.scale(scale); + canvas.drawRect(Rect.fromLTWH(left, top, maxCubeSize, maxCubeSize), paint); + + canvas.restore(); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/line_scale_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/line_scale_indicator.dart new file mode 100644 index 0000000..3784da5 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/line_scale_indicator.dart @@ -0,0 +1,139 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:loading_indicator_view_plus/src/infinite_progress.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-01 +/// + +class LineScaleIndicator extends StatefulWidget { + LineScaleIndicator({ + this.maxLength: 38, + this.minLength: 12, + this.spacing: 3.5, + this.lineWidth: 4, + this.lineNum: 5, + this.lineColor: Colors.white, + this.duration: const Duration(milliseconds: 400), + }); + + final double maxLength; + final double minLength; + final double lineWidth; + final double spacing; + final int lineNum; + final Color lineColor; + final Duration duration; + + @override + State createState() => _LineScaleIndicatorState(); +} + +class _LineScaleIndicatorState extends State + with SingleTickerProviderStateMixin, InfiniteProgressMixin { + @override + void initState() { + startEngine(this, widget.duration); + super.initState(); + } + + @override + void dispose() { + closeEngine(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + return CustomPaint( + size: measureSize(), + painter: _LineScaleIndicatorPainter( + animationValue: animationValue, + minLength: widget.minLength, + maxLength: widget.maxLength, + spacing: widget.spacing, + lineNum: widget.lineNum, + lineWidth: widget.lineWidth, + lineColor: widget.lineColor, + ), + ); + }, + ); + } + + @override + Size measureSize() { + var width = widget.lineNum * widget.lineWidth + + (widget.lineNum - 1) * widget.spacing; + return Size(width, widget.maxLength); + } +} + +double _progress = .0; +double _lastExtent = .0; + +class _LineScaleIndicatorPainter extends CustomPainter { + _LineScaleIndicatorPainter({ + required this.animationValue, + required this.minLength, + required this.maxLength, + required this.lineWidth, + required this.spacing, + required this.lineNum, + required this.lineColor, + }) { + offsetLength = []; + var diffLength = maxLength - minLength; + for (int i = 0; i < lineNum; i++) { + offsetLength.add(minLength + diffLength * 2 * i / 10.0); + } + } + + final double animationValue; + final double minLength; + final double maxLength; + final double lineWidth; + final double spacing; + final int lineNum; + final Color lineColor; + + late List offsetLength; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = lineColor + ..strokeJoin = StrokeJoin.round + ..strokeCap = StrokeCap.round; + + _progress += (_lastExtent - animationValue).abs(); + _lastExtent = animationValue; + if (_progress >= double.maxFinite) { + _progress = .0; + _lastExtent = .0; + } + + var diffLength = maxLength - minLength; + for (int i = 0; i < lineNum; i++) { + var offsetExtent = asin((offsetLength[i] - minLength) / diffLength); + var scaleLength = + sin(_progress * pi / 180 + offsetExtent).abs() * diffLength + + minLength; + var left = (lineWidth + spacing) * i; + var top = (maxLength - scaleLength) * .5; + Rect rect = Rect.fromLTWH(left, top, lineWidth, scaleLength); + RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(4.0)); + canvas.drawRRect(rRect, paint); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/line_scale_pulse_out_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/line_scale_pulse_out_indicator.dart new file mode 100644 index 0000000..39cf6b8 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/line_scale_pulse_out_indicator.dart @@ -0,0 +1,138 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:loading_indicator_view_plus/src/infinite_progress.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-01 +/// + +class LineScalePulseOutIndicator extends StatefulWidget { + LineScalePulseOutIndicator({ + this.maxLength: 42, + this.minLength: 14, + this.spacing: 3.5, + this.lineWidth: 4, + this.lineNum: 5, + this.lineColor: Colors.white, + this.duration: const Duration(milliseconds: 350), + }); + + final double maxLength; + final double minLength; + final double lineWidth; + final double spacing; + final int lineNum; + final Color lineColor; + final Duration duration; + + @override + State createState() => _LineScalePulseOutIndicatorState(); +} + +class _LineScalePulseOutIndicatorState extends State + with SingleTickerProviderStateMixin, InfiniteProgressMixin { + @override + void initState() { + startEngine(this, widget.duration); + super.initState(); + } + + @override + void dispose() { + closeEngine(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + return CustomPaint( + size: measureSize(), + painter: _LineScalePulseOutIndicatorPainter( + animationValue: animationValue, + minLength: widget.minLength, + maxLength: widget.maxLength, + spacing: widget.spacing, + lineNum: widget.lineNum, + lineWidth: widget.lineWidth, + lineColor: widget.lineColor, + ), + ); + }, + ); + } + + @override + Size measureSize() { + var width = widget.lineNum * widget.lineWidth + + (widget.lineNum - 1) * widget.spacing; + return Size(width, widget.maxLength); + } +} + +double _progress = .0; +double _lastExtent = .0; + +class _LineScalePulseOutIndicatorPainter extends CustomPainter { + _LineScalePulseOutIndicatorPainter({ + required this.animationValue, + required this.minLength, + required this.maxLength, + required this.lineWidth, + required this.spacing, + required this.lineNum, + required this.lineColor, + }) : offsetLength = [ + minLength + (maxLength - minLength) * .2, + minLength + (maxLength - minLength) * .8, + minLength, + minLength + (maxLength - minLength) * .8, + minLength + (maxLength - minLength) * .2, + ]; + + final double animationValue; + final double minLength; + final double maxLength; + final double lineWidth; + final double spacing; + final int lineNum; + final Color lineColor; + final List offsetLength; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = lineColor + ..strokeJoin = StrokeJoin.round + ..strokeCap = StrokeCap.round; + + _progress += (_lastExtent - animationValue).abs(); + _lastExtent = animationValue; + if (_progress >= double.maxFinite) { + _progress = .0; + _lastExtent = .0; + } + + var diffLength = maxLength - minLength; + for (int i = 0; i < lineNum; i++) { + var offsetExtent = asin((offsetLength[i] - minLength) / diffLength); + var scaleLength = + sin(_progress * pi / 180 + offsetExtent).abs() * diffLength + + minLength; + var left = (lineWidth + spacing) * i; + var top = (maxLength - scaleLength) * .5; + Rect rect = Rect.fromLTWH(left, top, lineWidth, scaleLength); + RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(4.0)); + canvas.drawRRect(rRect, paint); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/line_scale_pulse_out_rapid_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/line_scale_pulse_out_rapid_indicator.dart new file mode 100644 index 0000000..f2d2a1d --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/line_scale_pulse_out_rapid_indicator.dart @@ -0,0 +1,140 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:loading_indicator_view_plus/src/infinite_progress.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-01 +/// + +class LineScalePulseOutRapidIndicator extends StatefulWidget { + LineScalePulseOutRapidIndicator({ + this.maxLength: 38, + this.minLength: 12, + this.spacing: 3.5, + this.lineWidth: 4, + this.lineNum: 5, + this.lineColor: Colors.white, + this.duration: const Duration(milliseconds: 400), + }); + + final double maxLength; + final double minLength; + final double lineWidth; + final double spacing; + final int lineNum; + final Color lineColor; + final Duration duration; + + @override + State createState() => + _LineScalePulseOutRapidIndicatorState(); +} + +class _LineScalePulseOutRapidIndicatorState + extends State + with SingleTickerProviderStateMixin, InfiniteProgressMixin { + @override + void initState() { + startEngine(this, widget.duration); + super.initState(); + } + + @override + void dispose() { + closeEngine(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + return CustomPaint( + size: measureSize(), + painter: _LineScalePulseOutRapidIndicatorPainter( + animationValue: animationValue, + minLength: widget.minLength, + maxLength: widget.maxLength, + spacing: widget.spacing, + lineNum: widget.lineNum, + lineWidth: widget.lineWidth, + lineColor: widget.lineColor, + ), + ); + }, + ); + } + + @override + Size measureSize() { + var width = widget.lineNum * widget.lineWidth + + (widget.lineNum - 1) * widget.spacing; + return Size(width, widget.maxLength); + } +} + +double _progress = .0; +double _lastExtent = .0; + +class _LineScalePulseOutRapidIndicatorPainter extends CustomPainter { + _LineScalePulseOutRapidIndicatorPainter({ + required this.animationValue, + required this.minLength, + required this.maxLength, + required this.lineWidth, + required this.spacing, + required this.lineNum, + required this.lineColor, + }) : offsetLength = [ + minLength + (maxLength - minLength) * .3, + minLength + (maxLength - minLength) * .7, + minLength, + minLength + (maxLength - minLength) * .7, + minLength + (maxLength - minLength) * .3, + ]; + + final double animationValue; + final double minLength; + final double maxLength; + final double lineWidth; + final double spacing; + final int lineNum; + final Color lineColor; + final List offsetLength; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = lineColor + ..strokeJoin = StrokeJoin.round + ..strokeCap = StrokeCap.round; + + _progress += (_lastExtent - animationValue).abs(); + _lastExtent = animationValue; + if (_progress >= double.maxFinite) { + _progress = .0; + _lastExtent = .0; + } + + var diffLength = maxLength - minLength; + for (int i = 0; i < lineNum; i++) { + var offsetExtent = asin((offsetLength[i] - minLength) / diffLength); + var scaleLength = + sin(_progress * pi / 180 + offsetExtent).abs() * diffLength + + minLength; + var left = (lineWidth + spacing) * i; + var top = (maxLength - scaleLength) * .5; + Rect rect = Rect.fromLTWH(left, top, lineWidth, scaleLength); + RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(4.0)); + canvas.drawRRect(rRect, paint); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/line_spin_fade_loader_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/line_spin_fade_loader_indicator.dart new file mode 100644 index 0000000..7225385 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/line_spin_fade_loader_indicator.dart @@ -0,0 +1,154 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../infinite_progress.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-02 +/// + +class LineSpinFadeLoaderIndicator extends StatefulWidget { + LineSpinFadeLoaderIndicator({ + this.radius = 18, + this.minLineWidth: 2.4, + this.maxLineWidth: 4.8, + this.minLineHeight: 4.8, + this.maxLineHeight: 9.6, + this.minBallAlpha: 77, + this.maxBallAlpha: 255, + this.ballColor: Colors.white, + this.duration: const Duration(milliseconds: 500), + }); + + final double radius; + final double minLineWidth; + final double maxLineWidth; + final double minLineHeight; + final double maxLineHeight; + final double minBallAlpha; + final double maxBallAlpha; + final Color ballColor; + final Duration duration; + + @override + State createState() => _LineSpinFadeLoaderIndicatorState(); +} + +class _LineSpinFadeLoaderIndicatorState + extends State + with SingleTickerProviderStateMixin, InfiniteProgressMixin { + @override + void initState() { + startEngine(this, widget.duration); + super.initState(); + } + + @override + void dispose() { + closeEngine(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + return CustomPaint( + size: measureSize(), + painter: _LineSpinFadeLoaderIndicatorPainter( + animationValue: animationValue, + minLineWidth: widget.minLineWidth, + maxLineWidth: widget.maxLineWidth, + minLineHeight: widget.minLineHeight, + maxLineHeight: widget.maxLineHeight, + minAlpha: widget.minBallAlpha, + maxAlpha: widget.maxBallAlpha, + ballColor: widget.ballColor), + ); + }, + ); + } + + @override + Size measureSize() { + return Size(2 * widget.radius, 2 * widget.radius); + } +} + +double _progress = .0; +double _lastExtent = .0; + +class _LineSpinFadeLoaderIndicatorPainter extends CustomPainter { + _LineSpinFadeLoaderIndicatorPainter({ + required this.animationValue, + required this.minLineWidth, + required this.maxLineWidth, + required this.minLineHeight, + required this.maxLineHeight, + required this.minAlpha, + required this.maxAlpha, + required this.ballColor, + }); + + final double animationValue; + final double minLineWidth; + final double maxLineWidth; + final double minLineHeight; + final double maxLineHeight; + final double minAlpha; + final double maxAlpha; + final Color ballColor; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill; + + _progress += (_lastExtent - animationValue).abs(); + _lastExtent = animationValue; + if (_progress >= double.maxFinite) { + _progress = .0; + _lastExtent = .0; + } + + var diffAlpha = maxAlpha - minAlpha; + var diffWidth = maxLineWidth - minLineWidth; + var diffHeight = maxLineHeight - minLineHeight; + for (int i = 0; i < 8; i++) { + canvas.save(); + + var newProgress = _progress - i * 22.5; + var beatAlpha = sin(newProgress * pi / 180).abs() * diffAlpha + minAlpha; + paint.color = Color.fromARGB( + beatAlpha.round(), ballColor.red, ballColor.green, ballColor.blue); + var scaleWidth = + sin(newProgress * pi / 180).abs() * diffWidth + minLineWidth; + var scaleHeight = + sin(newProgress * pi / 180).abs() * diffHeight + minLineHeight; + var point = _circleAt(size.width * .5, size.height * .5, + size.width * .5 - maxLineWidth, i * pi / 4); + + canvas.translate(point.dx, point.dy); + canvas.rotate((90 + (i * 45)) * pi / 180); + Rect rect = Rect.fromLTWH( + -scaleWidth * .5, -scaleHeight * .5, scaleWidth, scaleHeight); + RRect rRect = RRect.fromRectAndRadius(rect, Radius.circular(4.0)); + canvas.drawRRect(rRect, paint); + + canvas.restore(); + } + } + + Offset _circleAt(double width, double height, double radius, double angle) { + var x = width + radius * (cos(angle)); + var y = height + radius * (sin(angle)); + return Offset(x, y); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/pacman_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/pacman_indicator.dart new file mode 100644 index 0000000..1d5735f --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/pacman_indicator.dart @@ -0,0 +1,130 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-04 +/// + +class PacmanIndicator extends StatefulWidget { + PacmanIndicator({ + this.radius: 16, + this.beanRadius: 4, + this.color: Colors.white, + this.duration: const Duration(milliseconds: 325), + }); + + final double radius; + final double beanRadius; + final Color color; + final Duration duration; + + @override + State createState() => _PacmanIndicatorState(); +} + +class _PacmanIndicatorState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation pacman; + late Animation bean; + + @override + void initState() { + _controller = AnimationController(vsync: this, duration: widget.duration); + pacman = Tween(begin: 0, end: 90) + .animate(CurvedAnimation(parent: _controller, curve: Curves.linear)); + bean = Tween(begin: 0, end: widget.radius * .5) + .animate(CurvedAnimation(parent: _controller, curve: Curves.linear)); + _controller.addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + _controller.reverse(); + } else if (status == AnimationStatus.dismissed) { + _controller.forward(); + } + }); + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Size _measureSize() { + var width = (widget.radius + widget.beanRadius) * 2; + var height = widget.radius * 2; + return Size(width, height); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, child) => CustomPaint( + size: _measureSize(), + painter: _PacmanIndicatorPainter( + pacmanAngle: pacman.value, + beanTransX: bean.value, + radius: widget.radius, + beanRadius: widget.beanRadius, + color: widget.color, + ), + )); + } +} + +double _progress = .0; +double _lastExtent = .0; + +class _PacmanIndicatorPainter extends CustomPainter { + _PacmanIndicatorPainter({ + required this.pacmanAngle, + required this.beanTransX, + required this.radius, + required this.beanRadius, + required this.color, + }); + + final double pacmanAngle; + final double beanTransX; + final double radius; + final double beanRadius; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = color; + + var width = radius * 2; + var height = radius * 2; + var radian = pi / 180; + Rect rect = Rect.fromLTWH(0, 0, width, height); + canvas.drawArc(rect, (0 + pacmanAngle * .5) * radian, + (360 - pacmanAngle) * radian, true, paint); + + _progress += (_lastExtent - beanTransX).abs(); + _lastExtent = beanTransX; + if (_progress >= radius) { + _progress = .0; + _lastExtent = .0; + } + + var beanAlpha = 255 - (122.5 * _progress / radius); + paint.color = + Color.fromARGB(beanAlpha.round(), color.red, color.green, color.blue); + + var cx = width + beanRadius; + var cy = size.height * .5; + canvas.drawCircle(Offset(cx - _progress, cy), beanRadius, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/semi_circle_spin_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/semi_circle_spin_indicator.dart new file mode 100644 index 0000000..24d182f --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/semi_circle_spin_indicator.dart @@ -0,0 +1,98 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-04 +/// + +class SemiCircleSpinIndicator extends StatefulWidget { + SemiCircleSpinIndicator({ + this.radius: 24, + this.color: Colors.white, + this.duration: const Duration(milliseconds: 600), + }); + + final double radius; + final Color color; + final Duration duration; + + @override + State createState() => _SemiCircleSpinIndicatorState(); +} + +class _SemiCircleSpinIndicatorState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + + @override + void initState() { + _controller = AnimationController(vsync: this, duration: widget.duration); + _animation = + CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn); + _animation = Tween(begin: 0, end: 360).animate(_animation) + ..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + _controller.reset(); + _controller.forward(); + } + }); + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Size _measureSize() { + return Size(2 * widget.radius, 2 * widget.radius); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return CustomPaint( + size: _measureSize(), + painter: _SemiCircleSpinIndicatorPainter( + animationValue: _animation.value, + color: widget.color, + ), + ); + }, + ); + } +} + +class _SemiCircleSpinIndicatorPainter extends CustomPainter { + _SemiCircleSpinIndicatorPainter({ + required this.animationValue, + required this.color, + }); + + final double animationValue; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = color; + + canvas.translate(size.width * .5, size.height * .5); + canvas.rotate(animationValue * pi / 180); + Rect rect = Rect.fromLTWH( + -size.width * .5, -size.height * .5, size.width, size.height); + canvas.drawArc(rect, -60 * pi / 180, 120 * pi / 180, false, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/square_spin_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/square_spin_indicator.dart new file mode 100644 index 0000000..4c95454 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/square_spin_indicator.dart @@ -0,0 +1,110 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-02 +/// + +class SquareSpinIndicator extends StatefulWidget { + SquareSpinIndicator({ + this.length = 36, + this.color = Colors.white, + this.duration = const Duration(milliseconds: 1250), + }); + + final double length; + final Color color; + final Duration duration; + + @override + State createState() => _SquareSpinIndicatorState(); +} + +class _SquareSpinIndicatorState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _rotateX, _rotateY; + + @override + void initState() { + _controller = AnimationController(vsync: this, duration: widget.duration); + _rotateX = Tween(begin: 0, end: 180) + .animate(CurvedAnimation(parent: _controller, curve: Interval(0, 0.5))); + _rotateY = Tween(begin: 0, end: 180) + .animate(CurvedAnimation(parent: _controller, curve: Interval(0.5, 1))); + _controller.addListener(() { + if (_controller.isCompleted) { + _controller.reset(); + _controller.forward(); + } + }); + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return CustomPaint( + size: _measureSize(), + painter: _SquareSpinIndicatorPainter( + rotateX: _rotateX.value, + rotateY: _rotateY.value, + color: widget.color, + ), + ); + }, + ); + } + + Size _measureSize() => Size(widget.length, widget.length); +} + +class _SquareSpinIndicatorPainter extends CustomPainter { + _SquareSpinIndicatorPainter({ + required this.rotateX, + required this.rotateY, + required this.color, + }); + + final double rotateX; + final double rotateY; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = color; + + final halfLength = size.width * .5; + var rect = Rect.fromLTWH(-halfLength, -halfLength, size.width, size.height); + + final radian = pi / 180; + var matrix; + if (rotateX < 180) { + matrix = Matrix4.rotationX(rotateX * radian); + } else if (rotateY < 180) { + matrix = Matrix4.rotationY(rotateY * radian); + } + + canvas.translate(halfLength, halfLength); + canvas.transform(matrix.storage); + canvas.drawRect(rect, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/triangle_skew_spin_indicator.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/triangle_skew_spin_indicator.dart new file mode 100644 index 0000000..4c389d3 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/indicators/triangle_skew_spin_indicator.dart @@ -0,0 +1,122 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +/// +/// author: Vans Z +/// date: 2019-06-08 +/// + +class TriangleSkewSpinIndicator extends StatefulWidget { + TriangleSkewSpinIndicator({ + this.width: 42, + this.height: 28, + this.color: Colors.white, + this.duration: const Duration(milliseconds: 1250), + }); + + final double width; + final double height; + final Color color; + final Duration duration; + + @override + State createState() => _TriangleSkewSpinIndicatorState(); +} + +class _TriangleSkewSpinIndicatorState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _rotateX, _rotateY; + + @override + void initState() { + _controller = AnimationController(vsync: this, duration: widget.duration); + _rotateY = Tween(begin: 0, end: 180) + .animate(CurvedAnimation(parent: _controller, curve: Interval(0, 0.5))); + _rotateX = Tween(begin: 0, end: 180) + .animate(CurvedAnimation(parent: _controller, curve: Interval(0.5, 1))); + _controller.addListener(() { + if (_controller.isCompleted) { + _controller.reset(); + _controller.forward(); + _count++; + } + }); + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => AnimatedBuilder( + animation: _controller, + builder: (context, child) => CustomPaint( + size: Size(widget.width, widget.height), + painter: _TriangleSkewSpinIndicatorPainter( + rotateX: _rotateX.value, + rotateY: _rotateY.value, + color: widget.color, + ), + ), + ); +} + +var _count = 0; + +class _TriangleSkewSpinIndicatorPainter extends CustomPainter { + _TriangleSkewSpinIndicatorPainter({ + required this.rotateX, + required this.rotateY, + required this.color, + }); + + final double rotateX; + final double rotateY; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill + ..color = color; + + final radian = pi / 180; + var matrix; + if (rotateY < 180) { + matrix = Matrix4.rotationY(rotateY * radian); + } else if (rotateX < 180) { + matrix = Matrix4.rotationX(rotateX * radian); + } + + final halfWidth = size.width * .5; + final halfHeight = size.height * .5; + + var path; + if (_count % 2 == 0) { + path = Path() + ..moveTo(0, halfHeight) + ..lineTo(halfWidth, -halfHeight) + ..lineTo(-halfWidth, -halfHeight) + ..close(); + } else { + path = Path() + ..moveTo(0, -halfHeight) + ..lineTo(halfWidth, halfHeight) + ..lineTo(-halfWidth, halfHeight) + ..close(); + } + canvas.translate(halfWidth, halfHeight); + canvas.transform(matrix.storage); + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/lib/src/infinite_progress.dart b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/infinite_progress.dart new file mode 100644 index 0000000..586cc9b --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/lib/src/infinite_progress.dart @@ -0,0 +1,33 @@ +import 'package:flutter/widgets.dart'; + +/// +/// author:Vans Z +/// date: 2019-06-01 +/// + +mixin InfiniteProgressMixin { + late Animation _animation; + late AnimationController controller; + + double get animationValue => _animation.value; + + void startEngine(TickerProvider vsync, Duration duration) { + controller = AnimationController(vsync: vsync, duration: duration); + _animation = CurvedAnimation(parent: controller, curve: Curves.linear); + _animation = Tween(begin: 0, end: 90).animate(_animation) + ..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + controller.reverse(); + } else if (status == AnimationStatus.dismissed) { + controller.forward(); + } + }); + controller.forward(); + } + + Size measureSize(); + + void closeEngine() { + controller?.dispose(); + } +} diff --git a/local_packages/loading_indicator_view_plus-2.0.0/pubspec.yaml b/local_packages/loading_indicator_view_plus-2.0.0/pubspec.yaml new file mode 100644 index 0000000..a2cf7f3 --- /dev/null +++ b/local_packages/loading_indicator_view_plus-2.0.0/pubspec.yaml @@ -0,0 +1,71 @@ +name: loading_indicator_view_plus +description: Flutter loading indicator widget (support null safety) +version: 2.0.0 +homepage: https://github.com/xtcel/loading_indicator_view_plus + +environment: + sdk: '>=2.18.0 <3.0.0' + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: com.xtcel.loading_indicator_view_plus.loading_indicator_view_plus + pluginClass: LoadingIndicatorViewPlusPlugin + ios: + pluginClass: LoadingIndicatorViewPlusPlugin + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/local_packages/on_audio_query_android-1.1.0/android/build.gradle b/local_packages/on_audio_query_android-1.1.0/android/build.gradle index d61c1a3..b83585c 100644 --- a/local_packages/on_audio_query_android-1.1.0/android/build.gradle +++ b/local_packages/on_audio_query_android-1.1.0/android/build.gradle @@ -2,14 +2,14 @@ group 'com.lucasjosino.on_audio_query' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '2.1.0' + ext.kotlin_version = '2.2.20' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:8.11.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/local_packages/tancent_vap-1.0.0+1/android/build.gradle b/local_packages/tancent_vap-1.0.0+1/android/build.gradle index 9e2f2e6..9ba17bd 100644 --- a/local_packages/tancent_vap-1.0.0+1/android/build.gradle +++ b/local_packages/tancent_vap-1.0.0+1/android/build.gradle @@ -2,14 +2,14 @@ group = "com.laskarmedia.vap" version = "1.0-SNAPSHOT" buildscript { - ext.kotlin_version = "2.1.0" + ext.kotlin_version = "2.2.20" repositories { google() mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:8.7.3") + classpath("com.android.tools.build:gradle:8.11.1") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") } } @@ -68,4 +68,4 @@ android { dependencies { // implementation("io.github.tencent:vap:2.0.28") implementation 'com.google.android.gms:play-services-basement:18.7.1' -} \ No newline at end of file +} diff --git a/md/后端接口对接规范.md b/md/后端接口对接规范.md new file mode 100644 index 0000000..f0271e3 --- /dev/null +++ b/md/后端接口对接规范.md @@ -0,0 +1,320 @@ +# 后端接口对接规范 + +## 1. 目标 + +本文档用于 Aslan Flutter 项目后续与后端接口对接时统一协作边界、接口事实来源、前端落地位置、记录格式和问题流转方式。 + +核心原则: + +- 前端可以只读查看后端代码、接口文档和契约,用于理解接口、字段、错误码、鉴权和业务边界。 +- 前端绝对不能修改后端仓库中的任何代码、文档、配置、生成文件、SQL、资源或测试。 +- 如果发现后端接口、字段、错误码或业务逻辑需要调整,只能整理建议和证据,由用户协调后端处理。 +- 对接接口时禁止本地假数据兜底;接口报错、空数据、权限失败、字段缺失都必须按真实状态处理,不能用本地 mock 数据伪装成接口成功。 + +--- + +## 2. 当前前端项目事实 + +当前 Flutter 项目路径: + +```text +/Users/pinpaibu/flutter_code/aslan-flutter +``` + +项目名与包: + +- 应用名:`Aslan` +- Flutter package import 前缀:`package:aslan/...` +- 当前配置入口:`lib/chatvibe_core/config/app_config.dart` +- 当前默认配置:`Variant1Config` +- 当前 API Host:`https://api.atuchat.com/` +- 当前图片 Host:`https://img.atuchat.com/` + +主要前端分层: + +- `lib/chatvibe_core/`:配置、常量、路由、通用工具。 +- `lib/chatvibe_data/`:数据层实现、本地存储、网络请求、仓库实现。 +- `lib/chatvibe_domain/`:仓库接口、请求模型、响应模型和领域模型。 +- `lib/chatvibe_features/`:按业务功能组织的页面、状态和交互逻辑。 +- `lib/chatvibe_managers/`:跨页面复用的管理器。 +- `lib/chatvibe_ui/`:公共 UI 组件、主题和通用 widgets。 +- `local_packages/`:项目内维护的本地 Flutter 插件,不能随意替换为 pub.dev 原版。 + +--- + +## 3. 后端代码位置 + +服务端代码项目名称:`likei-services`。 + +当前本机后端仓库路径: + +```text +/Users/pinpaibu/server_code/likei-services +``` + +已确认的后端结构: + +- `rc-gateway/`:服务网关,客户端接口对接优先看这里。 +- `rc-auth/`:认证服务。 +- `rc-service/rc-service-wallet/`:钱包服务。 +- `rc-service/rc-service-live/`:直播 / 语音房相关服务。 +- `rc-service/rc-service-order/`:订单服务。 +- `rc-service/rc-service-other/`:其他业务服务。 +- `rc-service/rc-service-external/`:外部能力对接服务。 +- `rc-service/rc-service-console/`:后台运营服务。 +- `rc-service/rc-inner-api/`:内部服务 API / DTO 契约。 +- `rc-common-business/`:通用业务组件、DTO、MQ、核心能力。 +- `rc-register/`:注册配置中心。 +- `rc-scheduling/`:调度中心。 +- `rc-service-game/`:游戏服务。 +- `claude/`、`reports/`:后端辅助资料或报告,按需只读参考。 + +```text +后端仓库类型:Java / Maven 多模块服务 +``` + +说明: + +- 后端代码仅用于接口 API 参考。 +- 不要修改服务端任何代码。 +- 如果服务端需要增加接口或者存在 bug,只能提出建议和证据。 + +--- + +## 4. 后端代码只读红线 + +后续任何前端线程、AI 或开发者,进入后端路径时必须遵守: + +- 只允许查看、搜索、摘录和分析后端代码。 +- 禁止创建、修改、删除、移动、格式化后端仓库中的任何文件。 +- 禁止对后端仓库使用 `apply_patch`、编辑器保存、`sed -i`、重定向写文件、脚本生成、批量替换等写入方式。 +- 禁止运行会改动后端仓库文件的命令,例如格式化、依赖整理、代码生成、数据库迁移生成、OpenAPI 生成、mock 生成。 +- 默认不在后端仓库执行构建、测试、启动、Docker、Makefile 或脚本命令;如果确实需要运行,必须先说明目的、可能产生的文件影响,并得到用户明确允许。 +- 禁止提交、暂存、切分支、rebase、merge、pull、push 后端仓库。 +- 禁止把前端对接需要的临时标记写入后端代码注释或文档。 + +允许的只读命令示例: + +```text +ls +find +rg +sed -n +git status --short +git diff --stat +git show +git grep +``` + +如果命令可能产生缓存、日志、生成文件或配置变更,默认视为禁止。 + +--- + +## 5. 接口事实参考顺序 + +对接某个接口前,必须按以下顺序确认事实: + +1. 先看后端接口文档或 OpenAPI,确认 HTTP path、method、请求体、响应体、鉴权要求和错误码描述。 +2. 再看后端实际路由注册和 handler/controller,确认当前线上实现、参数解析、响应 envelope 和鉴权行为。 +3. 如接口由内部服务 API、DTO、Feign/RPC 或其它内部契约支撑,再看对应契约,确认字段语义、枚举、默认值和跨服务约束。 +4. 必要时查看后端 service/domain/storage 代码,只为理解业务规则和字段来源,不直接照搬内部实现到前端。 +5. 最后再查看业务说明文档,用于确认流程边界、当前限制和后续计划。 + +当接口文档、handler、契约、业务文档之间不一致时: + +- 以当前后端 handler/controller 实现作为“现状事实”记录。 +- 同时把不一致点写入“待后端确认事项”。 +- 前端不得自行修改后端文档或代码来消除不一致。 + +--- + +## 6. 当前 Flutter 网络层约定 + +网络入口: + +- 全局请求实例:`lib/chatvibe_data/sources/remote/net/network_client.dart` 中的 `http` +- 通用请求基类:`lib/chatvibe_data/sources/remote/net/api.dart` 中的 `BaseNetworkClient` +- Dio 基础配置:`connectTimeout = 12s`,`receiveTimeout = 15s` +- 额外超时拦截:`TimeOutInterceptor.globalTimeoutSeconds = 15` + +基础 URL: + +- `NetworkClient.init()` 设置 `dio.options.baseUrl = ATGlobalConfig.apiHost` +- `ApiInterceptor.onRequest()` 每次请求会重新写入 `options.baseUrl = ATGlobalConfig.apiHost` +- `ATGlobalConfig.apiHost` 来自 `AppConfig.current.apiHost` + +请求头: + +- `req-lang`:当前语言。 +- `req-imei`:设备 ID。 +- `req-app-intel`:版本、build、机型、系统版本、渠道。 +- `req-client`:`Android` 或 `iOS`。 +- `req-sys-origin`:`origin` 与 `originChild`。 +- `Authorization`:当本地 token 非空时发送 `Bearer `。 +- `Req-Atyou`:当前固定为 `true`。 + +响应 envelope: + +```text +{ + "status": bool, + "errorCode": int?, + "errorCodeName": string?, + "errorMsg": string?, + "body": any, + "time": number? +} +``` + +响应处理规则: + +- `status == true` 才视为业务成功。 +- `body` 由调用方传入的 `fromJson` 解析为领域响应模型。 +- `status == false` 会通过 DioException 进入错误流程。 +- 业务错误码集中参考 `lib/chatvibe_data/models/enum/at_erro_code.dart`。 +- 不能在 UI 层直接解析后端 envelope;应在网络层、仓库层或 manager/provider 中处理。 + +特别注意: + +- 当前仓库实现中,许多实际请求 path 是加密或映射后的字符串,真实业务接口名通常写在方法注释里,例如 `///auth/account/login`。 +- 后续记录接口时,必须同时记录“业务接口名”和“当前实际请求 path”。 +- 如果需要新增接口,不要猜测加密 path 或随意复用旧 path,必须先确认后端契约和当前前端映射方式。 + +--- + +## 7. 前端对接落地流程 + +每次对接新接口时,按以下步骤执行: + +1. 确认当前线程负责的业务板块,不能越界顺手处理其它模块。 +2. 只读查看后端接口文档、实际 handler/controller、契约和相关业务说明。 +3. 在前端记录接口事实:业务接口名、当前实际请求 path、method、auth、request、response、错误码、后端证据文件。 +4. 按当前项目分层落地: + - `lib/chatvibe_data/sources/remote/net/`:只维护通用网络能力、拦截器和响应 envelope。 + - `lib/chatvibe_domain/models/req/`:新增或调整请求模型。 + - `lib/chatvibe_domain/models/res/`:新增或调整响应模型。 + - `lib/chatvibe_domain/repositories/`:声明业务仓库接口方法。 + - `lib/chatvibe_data/sources/repositories/`:实现仓库接口并调用 `http.get/post/put/delete`。 + - `lib/chatvibe_managers/` 或 `lib/chatvibe_features//`:编排页面流程、加载态、错误态和状态更新。 + - `lib/chatvibe_ui/`:只做通用 UI 组件,不放接口解析逻辑。 +5. 接口错误、空数据、鉴权失败、字段缺失必须展示真实状态或走既有错误流程,禁止本地假数据兜底。 +6. 如发现后端需要调整,写入“待后端确认事项”,明确“建议后端处理”,前端不改后端。 + +--- + +## 8. 本地假数据与兜底红线 + +对接真实接口时禁止: + +- 接口报错后返回本地固定列表、固定对象或 mock JSON,让页面看起来正常。 +- 字段缺失时在仓库层硬编码业务假值来绕过问题。 +- 后端未返回权限、余额、身份、房间状态等关键字段时,前端自行推断并继续提交后续业务请求。 +- 将调试用 mock、临时测试数据、接口样例数据留在正式逻辑中。 +- 为了避免页面空态,把接口失败当作空列表成功。 + +允许: + +- UI 展示空态、错误态、重试按钮、toast 或既有错误弹窗。 +- 在开发调试环境中使用明确隔离的 mock,但不得进入正式接口对接逻辑。 +- 对非关键展示字段使用产品确认过的展示默认值,例如空昵称展示 `--`;这不等同于业务数据兜底。 + +--- + +## 9. 接口事实记录模板 + +后续每个正式接入的接口,建议按以下格式记录在对应业务文档或本文件的接口记录区: + +```text +### 接口名称 + +- 业务板块: +- 后端来源: + - 接口文档 / OpenAPI: + - Handler / Controller: + - Proto / RPC / Service: +- 业务接口名: +- 当前实际请求 path: +- Method: +- Auth: +- Request: +- Response body: +- 业务错误码: +- 前端落地位置: + - repository interface: + - repository impl: + - request model: + - response model: + - manager / provider / feature logic: + - page / widget: +- 是否存在本地假数据兜底:否 +- 待后端确认: +- 当前状态:未接入 / 接入中 / 已接入 / 等后端确认 +``` + +--- + +## 10. 后端问题建议流转 + +如果前端对接时发现以下情况,不修改后端,只记录建议: + +- 接口文档与实际 handler/controller 实现不一致。 +- 字段缺失、字段含义不清、枚举值不完整。 +- 错误码无法支撑前端区分提示。 +- 接口返回结构不适合前端稳定渲染。 +- 鉴权、幂等、分页、排序、时间单位、空态规则不明确。 +- 后端当前只支持 mock 或占位实现。 +- 前端需要新增接口、合并接口或调整响应字段。 +- 当前前端需要真实接口,但后端尚未提供可用契约。 + +建议记录格式: + +```text +- 【待后端确认|业务板块|YYYY-MM-DD HH:mm CST】 + 问题: + 证据: + 前端影响: + 建议后端处理: +``` + +所有建议由用户协调后端团队处理;前端线程只负责说明影响、提供证据和调整前端对接方案。 + +--- + +## 11. 验证要求 + +接口对接完成后,至少给出以下验证: + +- 静态检查:按改动范围运行 `flutter analyze` 或说明无法运行的原因。 +- 模型解析:确认成功响应、空 body、字段缺失、错误响应不会导致非预期崩溃。 +- 业务路径:验证页面正常加载、刷新、分页、提交、失败提示等主路径。 +- 鉴权路径:验证未登录、token 失效或权限不足时走既有登录/错误流程。 +- 回归范围:说明本次改动是否影响同仓库其它方法、共享模型、网络拦截器或全局错误处理。 + +如果是 bug 修复线程,还必须先说明 Root Cause,得到用户确认后再改代码,并坚持最小改动。 + +--- + +## 12. 当前可优先参考的前端文件 + +对接接口时,前端优先查看: + +- `lib/chatvibe_data/sources/remote/net/network_client.dart` +- `lib/chatvibe_data/sources/remote/net/api.dart` +- `lib/chatvibe_data/sources/remote/net/at_logger.dart` +- `lib/chatvibe_data/models/enum/at_erro_code.dart` +- `lib/chatvibe_core/constants/at_global_config.dart` +- `lib/chatvibe_core/config/app_config.dart` +- `lib/chatvibe_core/config/configs/variant1_config.dart` +- `lib/chatvibe_domain/repositories/` +- `lib/chatvibe_data/sources/repositories/` +- `lib/chatvibe_domain/models/req/` +- `lib/chatvibe_domain/models/res/` + +后端优先查看: + +- `/Users/pinpaibu/server_code/likei-services/README.md` +- `/Users/pinpaibu/server_code/likei-services/rc-gateway/` +- `/Users/pinpaibu/server_code/likei-services/rc-auth/` +- `/Users/pinpaibu/server_code/likei-services/rc-service/` +- `/Users/pinpaibu/server_code/likei-services/rc-common-business/` +- `/Users/pinpaibu/server_code/likei-services/rc-service/rc-inner-api/` diff --git a/pubspec.lock b/pubspec.lock index 1061339..ad99b8e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -6,23 +6,23 @@ packages: description: name: _flutterfire_internals sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.59" agora_rtc_engine: dependency: "direct main" description: name: agora_rtc_engine - sha256: "6559294d18ce4445420e19dbdba10fb58cac955cd8f22dbceae26716e194d70e" - url: "https://pub.flutter-io.cn" + sha256: d9a88129b8fdc36ec5246331f36486b198dd7eb41ee68bfb54b289be8d60c6df + url: "https://pub.dev" source: hosted - version: "6.5.3" + version: "6.5.4" app_links: dependency: "direct main" description: name: app_links sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.4.1" app_links_linux: @@ -30,23 +30,23 @@ packages: description: name: app_links_linux sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.3" app_links_platform_interface: dependency: transitive description: name: app_links_platform_interface - sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" - url: "https://pub.flutter-io.cn" + sha256: "78a18580eecac98108d1eef52a7db668bc317714f5205e616973363326efe333" + url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" app_links_web: dependency: transitive description: name: app_links_web sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.4" archive: @@ -54,7 +54,7 @@ packages: description: name: archive sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.0.9" args: @@ -62,95 +62,95 @@ packages: description: name: args sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.7.0" async: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 - url: "https://pub.flutter-io.cn" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.1" audioplayers: dependency: transitive description: name: audioplayers - sha256: a72dd459d1a48f61a6fb9c0134dba26597c9236af40639ff0eb70eb4e0baab70 - url: "https://pub.flutter-io.cn" + sha256: "2ba4bb2944baacbdd5372ff8254a8e7feb8c10d7739545e392f5605a8f618745" + url: "https://pub.dev" source: hosted - version: "6.6.0" + version: "6.8.1" audioplayers_android: dependency: transitive description: name: audioplayers_android - sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" - url: "https://pub.flutter-io.cn" + sha256: f5ff5b15620fbab8cb0849e9636c48e2b96c3f0f71723bbbe2ad3c761b205f05 + url: "https://pub.dev" source: hosted - version: "5.2.1" + version: "5.3.0" audioplayers_darwin: dependency: transitive description: name: audioplayers_darwin - sha256: c994b3bb3a921e4904ac40e013fbc94488e824fd7c1de6326f549943b0b44a91 - url: "https://pub.flutter-io.cn" + sha256: "1ca553add991384ecf421b9569da850f3ab2472ffb83f6970b0416365abc51be" + url: "https://pub.dev" source: hosted - version: "6.4.0" + version: "6.5.0" audioplayers_linux: dependency: transitive description: name: audioplayers_linux - sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 - url: "https://pub.flutter-io.cn" + sha256: "15178b726b7cdee5364d0463c8d445630c4e0fb7d26612b73c767e7d25de9417" + url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "4.3.0" audioplayers_platform_interface: dependency: transitive description: name: audioplayers_platform_interface - sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" - url: "https://pub.flutter-io.cn" + sha256: "765f6f0e6dca55cb471c9483fc77700564b3484d19198aca4ebb5147c6c85acb" + url: "https://pub.dev" source: hosted - version: "7.1.1" + version: "7.2.0" audioplayers_web: dependency: transitive description: name: audioplayers_web - sha256: faa8fa6587f996a6f604433b53af44c57a1407d4fe8dff5766cf63d6875e8de9 - url: "https://pub.flutter-io.cn" + sha256: ae1e0103c865a03e273f6d13d97b93f5595eac09915729cd5e37ef96e2857319 + url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "5.3.0" audioplayers_windows: dependency: transitive description: name: audioplayers_windows - sha256: bafff2b38b6f6d331887558ba6e0a01c9c208d9dbb3ad0005234db065122a734 - url: "https://pub.flutter-io.cn" + sha256: a70ae82bba2dfcb6eb03dd4815d737a2d46d33ea5a96a03f535cfcaac490e413 + url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.4.1" back_button_interceptor: dependency: "direct main" description: name: back_button_interceptor sha256: b85977faabf4aeb95164b3b8bf81784bed4c54ea1aef90a036ab6927ecf80c5a - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "8.0.4" badges: dependency: "direct main" description: name: badges - sha256: a7b6bbd60dce418df0db3058b53f9d083c22cdb5132a052145dc267494df0b84 - url: "https://pub.flutter-io.cn" + sha256: cf1c88fb3777df69ccd630b80de5267f54efa4a39381b0404a7c03d56cb7c041 + url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.2.0" boolean_selector: dependency: transitive description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" cached_network_image: @@ -158,7 +158,7 @@ packages: description: name: cached_network_image sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.4.1" cached_network_image_platform_interface: @@ -166,7 +166,7 @@ packages: description: name: cached_network_image_platform_interface sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.1" cached_network_image_web: @@ -174,7 +174,7 @@ packages: description: name: cached_network_image_web sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.1" carousel_slider: @@ -182,31 +182,31 @@ packages: description: name: carousel_slider sha256: febf4b0163e0242adc13d7a863b04965351f59e7dfea56675c7c2caa7bcd7476 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.1.2" characters: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.flutter-io.cn" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff - url: "https://pub.flutter-io.cn" + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" cli_util: dependency: transitive description: name: cli_util sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.4.2" clock: @@ -214,15 +214,23 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + url: "https://pub.dev" + source: hosted + version: "1.2.1" collection: dependency: transitive description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.19.1" cookie_jar: @@ -230,23 +238,23 @@ packages: description: name: cookie_jar sha256: "963da02c1ef64cb5ac20de948c9e5940aa351f1e34a12b1d327c83d85b7e8fff" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.0.9" cross_file: dependency: transitive description: name: cross_file - sha256: "942a4791cd385a68ccb3b32c71c427aba508a1bb949b86dff2adbe4049f16239" - url: "https://pub.flutter-io.cn" + sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51" + url: "https://pub.dev" source: hosted - version: "0.3.5" + version: "0.3.5+4" crypto: dependency: transitive description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.7" csslib: @@ -254,31 +262,31 @@ packages: description: name: csslib sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.2" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.flutter-io.cn" + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.9" dbus: dependency: transitive description: name: dbus - sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 - url: "https://pub.flutter-io.cn" + sha256: "792974a4007974fbc5c1b5433eb2330a9db3e368c3f906253af4c007d0f49a91" + url: "https://pub.dev" source: hosted - version: "0.7.12" + version: "0.7.13" device_info_plus: dependency: "direct main" description: name: device_info_plus sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "10.1.2" device_info_plus_platform_interface: @@ -286,31 +294,31 @@ packages: description: name: device_info_plus_platform_interface sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.3" dio: dependency: "direct main" description: name: dio - sha256: b9d46faecab38fc8cc286f80bc4d61a3bb5d4ac49e51ed877b4d6706efe57b25 - url: "https://pub.flutter-io.cn" + sha256: ea2bad3c89a27635ce2d85cce4d6b199da49a5a48ec77b03e45b65a3b90922b0 + url: "https://pub.dev" source: hosted - version: "5.9.1" + version: "5.10.0" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" - url: "https://pub.flutter-io.cn" + sha256: dd58dc3861eb36edb13b217efc006a1c21e5bbc341de8c229b85634fa5e362e4 + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" event_bus: dependency: "direct main" description: name: event_bus sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.1" extended_image: @@ -318,7 +326,7 @@ packages: description: name: extended_image sha256: f6cbb1d798f51262ed1a3d93b4f1f2aa0d76128df39af18ecb77fa740f88b2e0 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "10.0.1" extended_image_library: @@ -326,7 +334,7 @@ packages: description: name: extended_image_library sha256: "1f9a24d3a00c2633891c6a7b5cab2807999eb2d5b597e5133b63f49d113811fe" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.0.1" extended_nested_scroll_view: @@ -334,7 +342,7 @@ packages: description: name: extended_nested_scroll_view sha256: "835580d40c2c62b448bd14adecd316acba469ba61f1510ef559d17668a85e777" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.2.1" extended_text: @@ -342,23 +350,22 @@ packages: description: name: extended_text sha256: d8f4a6e2676505b54dc0d5f5e8de9020667b402e9c1b3a8b030a83e568c99654 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "15.0.2" extended_text_field: dependency: "direct main" description: - name: extended_text_field - sha256: "3996195c117c6beb734026a7bc0ba80d7e4e84e4edd4728caa544d8209ab4d7d" - url: "https://pub.flutter-io.cn" - source: hosted + path: "local_packages/extended_text_field-16.0.2" + relative: true + source: path version: "16.0.2" extended_text_library: dependency: transitive description: name: extended_text_library sha256: "13d99f8a10ead472d5e2cf4770d3d047203fe5054b152e9eb5dc692a71befbba" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "12.0.1" fading_edge_scrollview: @@ -366,7 +373,7 @@ packages: description: name: fading_edge_scrollview sha256: "1f84fe3ea8e251d00d5735e27502a6a250e4aa3d3b330d3fdcb475af741464ef" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.1" fake_async: @@ -374,7 +381,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.3" ffi: @@ -382,7 +389,7 @@ packages: description: name: ffi sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" file: @@ -390,47 +397,47 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.1" file_selector_linux: dependency: transitive description: name: file_selector_linux - sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" - url: "https://pub.flutter-io.cn" + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" source: hosted - version: "0.9.3+2" + version: "0.9.4" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" - url: "https://pub.flutter-io.cn" + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" source: hosted - version: "0.9.4+4" + version: "0.9.5" file_selector_platform_interface: dependency: transitive description: name: file_selector_platform_interface sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.7.0" file_selector_windows: dependency: transitive description: name: file_selector_windows - sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" - url: "https://pub.flutter-io.cn" + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" source: hosted - version: "0.9.3+4" + version: "0.9.3+5" firebase_auth: dependency: "direct main" description: name: firebase_auth sha256: "0fed2133bee1369ee1118c1fef27b2ce0d84c54b7819a2b17dada5cfec3b03ff" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.7.0" firebase_auth_platform_interface: @@ -438,7 +445,7 @@ packages: description: name: firebase_auth_platform_interface sha256: "871c9df4ec9a754d1a793f7eb42fa3b94249d464cfb19152ba93e14a5966b386" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.7.3" firebase_auth_web: @@ -446,7 +453,7 @@ packages: description: name: firebase_auth_web sha256: d9ada769c43261fd1b18decf113186e915c921a811bd5014f5ea08f4cf4bc57e - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.15.3" firebase_core: @@ -454,23 +461,23 @@ packages: description: name: firebase_core sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.15.2" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 - url: "https://pub.flutter-io.cn" + sha256: "0ecda14c1bfc9ed8cac303dd0f8d04a320811b479362a9a4efb14fd331a473ce" + url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.0.3" firebase_core_web: dependency: transitive description: name: firebase_core_web sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.24.1" firebase_crashlytics: @@ -478,7 +485,7 @@ packages: description: name: firebase_crashlytics sha256: "662ae6443da91bca1fb0be8aeeac026fa2975e8b7ddfca36e4d90ebafa35dde1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.3.10" firebase_crashlytics_platform_interface: @@ -486,7 +493,7 @@ packages: description: name: firebase_crashlytics_platform_interface sha256: "7222a8a40077c79f6b8b3f3439241c9f2b34e9ddfde8381ffc512f7b2e61f7eb" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.8.10" fixnum: @@ -494,7 +501,7 @@ packages: description: name: fixnum sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" fluro: @@ -502,7 +509,7 @@ packages: description: name: fluro sha256: "24d07d0b285b213ec2045b83e85d076185fa5c23651e44dae0ac6755784b97d0" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.5" flutter: @@ -515,7 +522,7 @@ packages: description: name: flutter_cache_manager sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.4.1" flutter_debouncer: @@ -523,7 +530,7 @@ packages: description: name: flutter_debouncer sha256: "89f98f874e6abbb212f3027a7a27d5ce42c5b6544c8f5967d91140c0ae06ae22" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.1" flutter_foreground_task: @@ -538,7 +545,7 @@ packages: description: name: flutter_image_compress sha256: "51d23be39efc2185e72e290042a0da41aed70b14ef97db362a6b5368d0523b27" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.0" flutter_image_compress_common: @@ -546,7 +553,7 @@ packages: description: name: flutter_image_compress_common sha256: c5c5d50c15e97dd7dc72ff96bd7077b9f791932f2076c5c5b6c43f2c88607bfb - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.6" flutter_image_compress_macos: @@ -554,7 +561,7 @@ packages: description: name: flutter_image_compress_macos sha256: "20019719b71b743aba0ef874ed29c50747461e5e8438980dfa5c2031898f7337" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.3" flutter_image_compress_ohos: @@ -562,7 +569,7 @@ packages: description: name: flutter_image_compress_ohos sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.0.3" flutter_image_compress_platform_interface: @@ -570,7 +577,7 @@ packages: description: name: flutter_image_compress_platform_interface sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.5" flutter_image_compress_web: @@ -578,7 +585,7 @@ packages: description: name: flutter_image_compress_web sha256: b9b141ac7c686a2ce7bb9a98176321e1182c9074650e47bb140741a44b6f5a96 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.1.5" flutter_launcher_icons: @@ -586,7 +593,7 @@ packages: description: name: flutter_launcher_icons sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.14.4" flutter_lints: @@ -594,7 +601,7 @@ packages: description: name: flutter_lints sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.0.0" flutter_localizations: @@ -613,42 +620,42 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476 - url: "https://pub.flutter-io.cn" + sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785" + url: "https://pub.dev" source: hosted - version: "2.0.31" + version: "2.0.35" flutter_screenutil: dependency: "direct main" description: name: flutter_screenutil sha256: "8239210dd68bee6b0577aa4a090890342d04a136ce1c81f98ee513fc0ce891de" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.9.3" flutter_smart_dialog: dependency: "direct main" description: name: flutter_smart_dialog - sha256: "0852df132cb03fd8fc5144eb404c31eb7eb50c22aecb1cc2504f2f598090d756" - url: "https://pub.flutter-io.cn" + sha256: "72762b20c25f8e12d490332004c9cca327f39dfc1fcba66124c6b7c108b68850" + url: "https://pub.dev" source: hosted - version: "4.9.8+9" + version: "4.9.8+10" flutter_staggered_grid_view: dependency: "direct main" description: name: flutter_staggered_grid_view sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.0" flutter_svga: dependency: "direct main" description: name: flutter_svga - sha256: a7de69f2b51aea08b2f7e1dfc6c078beb3b677a43f966bfc703ed9199e70ffd0 - url: "https://pub.flutter-io.cn" + sha256: c914ba2aee5e2d53775b64c7ff6530b4cdc3c27fd9348debb0d86fd68b869c39 + url: "https://pub.dev" source: hosted - version: "0.0.5" + version: "0.0.13" flutter_test: dependency: "direct dev" description: flutter @@ -664,7 +671,7 @@ packages: description: name: fluttertoast sha256: "90778fe0497fe3a09166e8cf2e0867310ff434b794526589e77ec03cf08ba8e8" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "8.2.14" google_identity_services_web: @@ -672,7 +679,7 @@ packages: description: name: google_identity_services_web sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.3.3+1" google_sign_in: @@ -680,7 +687,7 @@ packages: description: name: google_sign_in sha256: d0a2c3bcb06e607bb11e4daca48bd4b6120f0bbc4015ccebbe757d24ea60ed2a - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.3.0" google_sign_in_android: @@ -688,7 +695,7 @@ packages: description: name: google_sign_in_android sha256: d5e23c56a4b84b6427552f1cf3f98f716db3b1d1a647f16b96dbb5b93afa2805 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.2.1" google_sign_in_ios: @@ -696,7 +703,7 @@ packages: description: name: google_sign_in_ios sha256: "102005f498ce18442e7158f6791033bbc15ad2dcc0afa4cf4752e2722a516c96" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.9.0" google_sign_in_platform_interface: @@ -704,7 +711,7 @@ packages: description: name: google_sign_in_platform_interface sha256: "5f6f79cf139c197261adb6ac024577518ae48fdff8e53205c5373b5f6430a8aa" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.0" google_sign_in_web: @@ -712,23 +719,31 @@ packages: description: name: google_sign_in_web sha256: "460547beb4962b7623ac0fb8122d6b8268c951cf0b646dd150d60498430e4ded" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.12.4+4" gtk: dependency: transitive description: name: gtk - sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c - url: "https://pub.flutter-io.cn" + sha256: "4ff85b2a16724029dd9e5bbb5a94b6918f9973f74ba571c949d2002801879cf5" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" + hooks: + dependency: transitive + description: + name: hooks + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + url: "https://pub.dev" + source: hosted + version: "2.0.2" html: dependency: transitive description: name: html sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.15.6" http: @@ -736,7 +751,7 @@ packages: description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.6.0" http_client_helper: @@ -744,7 +759,7 @@ packages: description: name: http_client_helper sha256: "8a9127650734da86b5c73760de2b404494c968a3fd55602045ffec789dac3cb1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.0" http_parser: @@ -752,7 +767,7 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.2" id3: @@ -760,31 +775,30 @@ packages: description: name: id3 sha256: "24176a6e08db6297c8450079e94569cd8387f913c817e5e3d862be7dc191e0b8" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.2" image: dependency: transitive description: name: image - sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce - url: "https://pub.flutter-io.cn" + sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52" + url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.9.1" image_cropper: dependency: "direct main" description: - name: image_cropper - sha256: f4bad5ed2dfff5a7ce0dfbad545b46a945c702bb6182a921488ef01ba7693111 - url: "https://pub.flutter-io.cn" - source: hosted + path: "local_packages/image_cropper-5.0.1" + relative: true + source: path version: "5.0.1" image_cropper_for_web: dependency: transitive description: name: image_cropper_for_web sha256: "865d798b5c9d826f1185b32e5d0018c4183ddb77b7b82a931e1a06aa3b74974e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.0" image_cropper_platform_interface: @@ -792,63 +806,63 @@ packages: description: name: image_cropper_platform_interface sha256: ee160d686422272aa306125f3b6fb1c1894d9b87a5e20ed33fa008e7285da11e - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.0.0" image_picker: dependency: "direct main" description: name: image_picker - sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" - url: "https://pub.flutter-io.cn" + sha256: d8402284df184bc05f4a2210c6c23983b0720f4cd87cbd05c5390a78af602667 + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.3" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" - url: "https://pub.flutter-io.cn" + sha256: "6f3a1995eafb000333174fae92202622033b0ee7fd917a6cd3730295264df84a" + url: "https://pub.dev" source: hosted - version: "0.8.13+1" + version: "0.8.13+19" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" - url: "https://pub.flutter-io.cn" + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e - url: "https://pub.flutter-io.cn" + sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 + url: "https://pub.dev" source: hosted - version: "0.8.13" + version: "0.8.13+6" image_picker_linux: dependency: transitive description: name: image_picker_linux sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.2" image_picker_macos: dependency: transitive description: name: image_picker_macos - sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 - url: "https://pub.flutter-io.cn" + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "0.2.2+1" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.11.1" image_picker_windows: @@ -856,7 +870,7 @@ packages: description: name: image_picker_windows sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.2" image_pixels: @@ -864,87 +878,103 @@ packages: description: name: image_pixels sha256: c88bb386a9067f489c9e9a9701ddb48e456e3f92c8c4ce575fac062809a1daae - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.0.2" in_app_purchase: dependency: "direct main" description: name: in_app_purchase - sha256: "5cddd7f463f3bddb1d37a72b95066e840d5822d66291331d7f8f05ce32c24b6c" - url: "https://pub.flutter-io.cn" + sha256: "0e9510b80b0074e89ab0a8e0fc901439b779dc9ae575ab8d419253c6e1627716" + url: "https://pub.dev" source: hosted - version: "3.2.3" + version: "3.3.0" in_app_purchase_android: dependency: transitive description: name: in_app_purchase_android - sha256: "67f135ca215b8cd84eca3ea46b934607709ab6826a8473129f01b12fd235c8c6" - url: "https://pub.flutter-io.cn" + sha256: eb8f551039481d1b265f12fa54f5ab5dd4f13ec5444a468b85a3793517a37fda + url: "https://pub.dev" source: hosted - version: "0.4.0+5" + version: "0.5.1" in_app_purchase_platform_interface: dependency: transitive description: name: in_app_purchase_platform_interface - sha256: "1d353d38251da5b9fea6635c0ebfc6bb17a2d28d0e86ea5e083bf64244f1fb4c" - url: "https://pub.flutter-io.cn" + sha256: "0b0076cac8ce4fa7048f01e76af8b123aeb6a7c4e0dea2a5206d6664454f3e36" + url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" in_app_purchase_storekit: dependency: transitive description: name: in_app_purchase_storekit - sha256: aedbeea5beae10af3e5c380b65049842715b2bb014983e2a48b9006473f33cd9 - url: "https://pub.flutter-io.cn" + sha256: "47d63717270133fcfa1ff8e144f6aaf9d498fa3884de43e24909737da55aedd8" + url: "https://pub.dev" source: hosted - version: "0.4.4" + version: "0.4.10+1" intl: dependency: transitive description: name: intl sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.20.2" iris_method_channel: dependency: transitive description: name: iris_method_channel - sha256: bfb5cfc6c6eae42da8cd1b35977a72d8b8881848a5dfc3d672e4760a907d11a0 - url: "https://pub.flutter-io.cn" + sha256: "114bbe541369add8dd0727858e7df5764f375e3fb88374ad487301733fddb57f" + url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.5" isolate_easy_pool: dependency: "direct main" description: name: isolate_easy_pool sha256: f0204cfdecbb84d61c46240a603bb21c3b2ac925475faf3f4afe18526fcb8f64 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.0.8" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" js: dependency: transitive description: name: js sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.6.7" json_annotation: dependency: transitive description: name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.flutter-io.cn" + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" source: hosted - version: "4.9.0" + version: "4.12.0" leak_tracker: dependency: transitive description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -952,7 +982,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.10" leak_tracker_testing: @@ -960,7 +990,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.2" lints: @@ -968,55 +998,62 @@ packages: description: name: lints sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.1.1" loading_indicator_view_plus: dependency: "direct main" description: - name: loading_indicator_view_plus - sha256: "23ad380dd677e8e0182f679f29e1221269ad66b9a1a2d70b19e716ee4723c99e" - url: "https://pub.flutter-io.cn" - source: hosted + path: "local_packages/loading_indicator_view_plus-2.0.0" + relative: true + source: path version: "2.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" marquee: dependency: "direct main" description: name: marquee sha256: a87e7e80c5d21434f90ad92add9f820cf68be374b226404fe881d2bba7be0862 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.flutter-io.cn" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.flutter-io.cn" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.flutter-io.cn" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.18.0" mime: dependency: transitive description: name: mime sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.0" mime_type: @@ -1024,7 +1061,7 @@ packages: description: name: mime_type sha256: d652b613e84dac1af28030a9fba82c0999be05b98163f9e18a0849c6e63838bb - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.1" nested: @@ -1032,15 +1069,23 @@ packages: description: name: nested sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + url: "https://pub.dev" + source: hosted + version: "9.4.1" octo_image: dependency: transitive description: name: octo_image sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.0" on_audio_query: @@ -1062,7 +1107,7 @@ packages: description: name: on_audio_query_ios sha256: "9b3efa39a656fa3720980e3c6a1f55b7257d0032a45ffeb3f70eaa2c7f10f929" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" on_audio_query_platform_interface: @@ -1070,7 +1115,7 @@ packages: description: name: on_audio_query_platform_interface sha256: c23e019a31bd0774828476e428fd33b0dd1d82c9d4791dba80429358fc65dcd3 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.7.0" on_audio_query_web: @@ -1078,15 +1123,23 @@ packages: description: name: on_audio_query_web sha256: "990efa52d879e6caffa97f24b34acd9caa1ce2c4c4cb873fe5a899a9b1af02c7" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.6.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" package_info_plus: dependency: "direct main" description: name: package_info_plus sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "8.3.1" package_info_plus_platform_interface: @@ -1094,7 +1147,7 @@ packages: description: name: package_info_plus_platform_interface sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.1" path: @@ -1102,7 +1155,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.9.1" path_drawing: @@ -1110,7 +1163,7 @@ packages: description: name: path_drawing sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.1" path_parsing: @@ -1118,87 +1171,87 @@ packages: description: name: path_parsing sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" path_provider: dependency: "direct main" description: name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.flutter-io.cn" + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.6" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" - url: "https://pub.flutter-io.cn" + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" source: hosted - version: "2.2.19" + version: "2.3.1" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" - url: "https://pub.flutter-io.cn" + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.6.0" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.flutter-io.cn" + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" + url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.flutter-io.cn" + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_windows: dependency: transitive description: name: path_provider_windows sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.0" permission_handler: dependency: "direct main" description: name: permission_handler - sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 - url: "https://pub.flutter-io.cn" + sha256: fe54465bcc62a4564c6e4db337bbaded6c0c0fa6e10487414436d163114784f6 + url: "https://pub.dev" source: hosted - version: "12.0.1" + version: "12.0.3" permission_handler_android: dependency: transitive description: name: permission_handler_android sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "13.0.1" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 - url: "https://pub.flutter-io.cn" + sha256: "79dfa1df734798aa3cfdad166d3a3698c206d8813de13516ea1071b5d7e2f420" + url: "https://pub.dev" source: hosted - version: "9.4.7" + version: "9.4.10" permission_handler_html: dependency: transitive description: name: permission_handler_html sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.1.3+5" permission_handler_platform_interface: @@ -1206,7 +1259,7 @@ packages: description: name: permission_handler_platform_interface sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.3.0" permission_handler_windows: @@ -1214,23 +1267,23 @@ packages: description: name: permission_handler_windows sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.1" petitparser: dependency: transitive description: name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" - url: "https://pub.flutter-io.cn" + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.0.2" photo_view: dependency: "direct main" description: name: photo_view sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.15.0" platform: @@ -1238,7 +1291,7 @@ packages: description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.6" plugin_platform_interface: @@ -1246,7 +1299,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.8" posix: @@ -1254,7 +1307,7 @@ packages: description: name: posix sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.5.0" pretty_dio_logger: @@ -1262,31 +1315,39 @@ packages: description: name: pretty_dio_logger sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" protobuf: dependency: transitive description: name: protobuf - sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" - url: "https://pub.flutter-io.cn" + sha256: "75ec242d22e950bdcc79ee38dd520ce4ee0bc491d7fadc4ea47694604d22bf06" + url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "6.0.0" provider: dependency: "direct main" description: name: provider sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" pull_to_refresh: dependency: "direct main" description: name: pull_to_refresh sha256: bbadd5a931837b57739cf08736bea63167e284e71fb23b218c8c9a6e042aad12 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.0" readmore: @@ -1294,63 +1355,71 @@ packages: description: name: readmore sha256: e8fca2bd397b86342483b409e2ec26f06560a5963aceaa39b27f30722b506187 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" rxdart: dependency: transitive description: name: rxdart sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.28.0" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" - url: "https://pub.flutter-io.cn" + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.5" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e - url: "https://pub.flutter-io.cn" + sha256: "93ae5884a9df5d3bb696825bceb3a17590754548b5d740eba51500afc8d088f5" + url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.4.26" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" - url: "https://pub.flutter-io.cn" + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.6" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.flutter-io.cn" + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" shared_preferences_web: dependency: transitive description: name: shared_preferences_web sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.3" shared_preferences_windows: @@ -1358,7 +1427,7 @@ packages: description: name: shared_preferences_windows sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" sign_in_with_apple: @@ -1366,7 +1435,7 @@ packages: description: name: sign_in_with_apple sha256: "8bd875c8e8748272749eb6d25b896f768e7e9d60988446d543fe85a37a2392b8" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.1" sign_in_with_apple_platform_interface: @@ -1374,7 +1443,7 @@ packages: description: name: sign_in_with_apple_platform_interface sha256: "981bca52cf3bb9c3ad7ef44aace2d543e5c468bb713fd8dda4275ff76dfa6659" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.0" sign_in_with_apple_web: @@ -1382,7 +1451,7 @@ packages: description: name: sign_in_with_apple_web sha256: f316400827f52cafcf50d00e1a2e8a0abc534ca1264e856a81c5f06bd5b10fed - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.0" sky_engine: @@ -1395,63 +1464,63 @@ packages: description: name: social_sharing_plus sha256: d0bd9dc3358cf66a3aac7cce549902360f30bccf44cf6090a708d97cc54ca854 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.3" source_span: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.flutter-io.cn" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" sqflite: dependency: transitive description: name: sqflite - sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 - url: "https://pub.flutter-io.cn" + sha256: "58a799e6ac17dd32fbab93813d39ed835a75ccc0f8f85b8955fe318c6712b082" + url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" sqflite_android: dependency: transitive description: name: sqflite_android - sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" - url: "https://pub.flutter-io.cn" + sha256: d0548f9d7422a2dae99ec6f8b0a3074463b132d216fa5ba0d230eeefc901983b + url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.3" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" - url: "https://pub.flutter-io.cn" + sha256: "5bf6a55c166e73bf651ba7ec3ed486e577620e3dc8f3a9c6a258a8031b624590" + url: "https://pub.dev" source: hosted - version: "2.5.5" + version: "2.5.11" sqflite_darwin: dependency: transitive description: name: sqflite_darwin - sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" - url: "https://pub.flutter-io.cn" + sha256: c86ca18b8f666bbf903924687fe21cc16fc385d086005067e26619ca530bef9f + url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3+1" sqflite_platform_interface: dependency: transitive description: name: sqflite_platform_interface - sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" - url: "https://pub.flutter-io.cn" + sha256: f84939f84350d92d04416f8bc4dc52d3896aec7716cc9e80cf0146342139dc50 + url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" stack_trace: dependency: transitive description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.12.1" store_redirect: @@ -1459,7 +1528,7 @@ packages: description: name: store_redirect sha256: ec635bc2f89c1f19b2f384784ddbfc8eb46b27d28224db32cadfe3820eeea6ca - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.4" stream_channel: @@ -1467,7 +1536,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" string_scanner: @@ -1475,17 +1544,17 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.1" synchronized: dependency: transitive description: name: synchronized - sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" - url: "https://pub.flutter-io.cn" + sha256: "93b153dcb6a26dcddee6ca087dd634b53e38c10b5aa163e8e49501a776456153" + url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.1" tancent_vap: dependency: "direct main" description: @@ -1498,7 +1567,7 @@ packages: description: name: tencent_cloud_chat_sdk sha256: fb930c86017fa4a546f3d0c15bc5a54197f07f003934737e80cf2f9a70cab1ba - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "8.3.6498+3" term_glyph: @@ -1506,23 +1575,23 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" - url: "https://pub.flutter-io.cn" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.11" typed_data: dependency: transitive description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" universal_io: @@ -1530,7 +1599,7 @@ packages: description: name: universal_io sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.1" url_launcher: @@ -1538,71 +1607,71 @@ packages: description: name: url_launcher sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.3.2" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" - url: "https://pub.flutter-io.cn" + sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 + url: "https://pub.dev" source: hosted - version: "6.3.20" + version: "6.3.32" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 - url: "https://pub.flutter-io.cn" + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" source: hosted - version: "6.3.4" + version: "6.4.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" - url: "https://pub.flutter-io.cn" + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f - url: "https://pub.flutter-io.cn" + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" source: hosted - version: "3.2.3" + version: "3.2.5" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" - url: "https://pub.flutter-io.cn" + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" - url: "https://pub.flutter-io.cn" + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" uuid: dependency: "direct main" description: name: uuid sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.5.3" vector_math: @@ -1610,47 +1679,47 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" video_player: dependency: "direct main" description: name: video_player - sha256: "096bc28ce10d131be80dfb00c223024eb0fba301315a406728ab43dd99c45bdf" - url: "https://pub.flutter-io.cn" + sha256: "48a7bdaa38a3d50ec10c78627abdbfad863fdf6f0d6e08c7c3c040cfd80ae36f" + url: "https://pub.dev" source: hosted - version: "2.10.1" + version: "2.11.1" video_player_android: dependency: transitive description: name: video_player_android - sha256: a8dc4324f67705de057678372bedb66cd08572fe7c495605ac68c5f503324a39 - url: "https://pub.flutter-io.cn" + sha256: "15bfd0598f8d8946bf0e2d50e2ca1a0db9491589a8bb7f6f9a388c0b48aef764" + url: "https://pub.dev" source: hosted - version: "2.8.15" + version: "2.10.0" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: f9a780aac57802b2892f93787e5ea53b5f43cc57dc107bee9436458365be71cd - url: "https://pub.flutter-io.cn" + sha256: "76097729ef0c976937945afa53f1ca3afa9b50c9a95909ba347bcf93270466fd" + url: "https://pub.dev" source: hosted - version: "2.8.4" + version: "2.10.0" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - sha256: "57c5d73173f76d801129d0531c2774052c5a7c11ccb962f1830630decd9f24ec" - url: "https://pub.flutter-io.cn" + sha256: e4ae5bc934b528e5b95c5e47be2812860186260cd3eed3ac62f5ed380fdd1613 + url: "https://pub.dev" source: hosted - version: "6.6.0" + version: "6.8.0" video_player_web: dependency: transitive description: name: video_player_web sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.0" video_thumbnail: @@ -1658,7 +1727,7 @@ packages: description: name: video_thumbnail sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.5.6" visibility_detector: @@ -1666,39 +1735,39 @@ packages: description: name: visibility_detector sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.4.0+2" vm_service: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" - url: "https://pub.flutter-io.cn" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.2.0" wakelock_plus: dependency: "direct main" description: name: wakelock_plus sha256: "61713aa82b7f85c21c9f4cd0a148abd75f38a74ec645fcb1e446f882c82fd09b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.3" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" - url: "https://pub.flutter-io.cn" + sha256: b13f99e992e7ae6a152e16c5559d3c07ff445b13330192662494e614ca3e7d7b + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.1" web: dependency: transitive description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" webview_flutter: @@ -1719,32 +1788,32 @@ packages: dependency: transitive description: name: webview_flutter_platform_interface - sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0" - url: "https://pub.flutter-io.cn" + sha256: "1221c1b12f5278791042f2ec2841743784cf25c5a644e23d6680e5d718824f04" + url: "https://pub.dev" source: hosted - version: "2.14.0" + version: "2.15.1" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - sha256: fb46db8216131a3e55bcf44040ca808423539bc6732e7ed34fb6d8044e3d512f - url: "https://pub.flutter-io.cn" + sha256: c879dd64b87c452aa84381b244d5469da57ba7e8cca6884c7b1e0d406372c12d + url: "https://pub.dev" source: hosted - version: "3.23.0" + version: "3.26.0" win32: dependency: transitive description: name: win32 - sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" - url: "https://pub.flutter-io.cn" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "5.15.0" win32_registry: dependency: transitive description: name: win32_registry sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.5" xdg_directories: @@ -1752,25 +1821,25 @@ packages: description: name: xdg_directories sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" xml: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.flutter-io.cn" + sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" + url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "7.0.1" yaml: dependency: transitive description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0-0 <4.0.0" - flutter: ">=3.29.0" + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0138415..8f4b294 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.6+7 +version: 1.0.7+8 environment: @@ -90,8 +90,10 @@ dependencies: # wechat_camera_picker: ^4.2.0 image_picker: ^1.2.0 #图片裁剪 - image_cropper: ^5.0.0 - loading_indicator_view_plus: ^2.0.0 + image_cropper: + path: ./local_packages/image_cropper-5.0.1 + loading_indicator_view_plus: + path: ./local_packages/loading_indicator_view_plus-2.0.0 back_button_interceptor: ^8.0.4 #vap特效 # flutter_vap_plus: ^1.2.10 @@ -113,7 +115,8 @@ dependencies: flutter_debouncer: ^2.0.0 #弹框 flutter_smart_dialog: ^4.9.8+9 - extended_text_field: 16.0.2 + extended_text_field: + path: ./local_packages/extended_text_field-16.0.2 # flutter_sound: 9.28.0 #图片压缩 flutter_image_compress: ^2.4.0 @@ -188,4 +191,4 @@ flutter: - atu_images/coupon/ - atu_images/family/ - atu_images/dynamic/ - - fonts/ \ No newline at end of file + - fonts/