guanghuispark
3 年前
当前提交
9f000197
共有 58 个文件被更改,包括 6699 次插入 和 49 次删除
-
99AwesomeUIWidgets/Assets/Scenes/SimpleWorldSpaceUI.unity
-
3AwesomeUIWidgets/Assets/Scripts/CountDemo.cs
-
675AwesomeUIWidgets/Assets/Scenes/ChatRoomScene.unity
-
335AwesomeUIWidgets/Assets/Scripts/ChatPage.cs
-
144AwesomeUIWidgets/Assets/Scripts/DateAndTimePicker.cs
-
74AwesomeUIWidgets/Assets/Scripts/Utils/FlyCamera.cs
-
64AwesomeUIWidgets/Assets/Scripts/chat_l10n.cs
-
487AwesomeUIWidgets/Assets/Scripts/chat_theme.cs
-
228AwesomeUIWidgets/Assets/Scripts/utils.cs
-
132AwesomeUIWidgets/Assets/Resources/assets/messages.json
-
525AwesomeUIWidgets/Assets/Scripts/AdditionalWidgets/LinkPreview/linkpreview.cs
-
140AwesomeUIWidgets/Assets/Scripts/AdditionalWidgets/LinkPreview/url_linkifier.cs
-
241AwesomeUIWidgets/Assets/Scripts/AdditionalWidgets/circleavatar.cs
-
135AwesomeUIWidgets/Assets/Scripts/ChatRoom/Message.cs
-
138AwesomeUIWidgets/Assets/Scripts/ChatRoom/Room.cs
-
147AwesomeUIWidgets/Assets/Scripts/ChatRoom/User.cs
-
49AwesomeUIWidgets/Assets/Scripts/ChatRoom/Util.cs
-
134AwesomeUIWidgets/Assets/Scripts/ChatRoom/preview_data.cs
-
156AwesomeUIWidgets/Assets/Scripts/ChatRoom/Messages/file_message.cs
-
164AwesomeUIWidgets/Assets/Scripts/ChatRoom/Messages/image_message.cs
-
62AwesomeUIWidgets/Assets/Scripts/ChatRoom/Messages/partial_file.cs
-
70AwesomeUIWidgets/Assets/Scripts/ChatRoom/Messages/partial_image.cs
-
36AwesomeUIWidgets/Assets/Scripts/ChatRoom/Messages/partial_text.cs
-
131AwesomeUIWidgets/Assets/Scripts/ChatRoom/Messages/text_message.cs
-
102AwesomeUIWidgets/Assets/Scripts/ChatRoom/Messages/unsupported_message.cs
-
97AwesomeUIWidgets/Assets/Scripts/ChatRoom/Messages/custom_message.cs
-
135AwesomeUIWidgets/Assets/Scripts/Conditional/conditional.cs
-
98AwesomeUIWidgets/Assets/Scripts/Equatable/equatable.cs
-
44AwesomeUIWidgets/Assets/Scripts/Equatable/equatable_config.cs
-
73AwesomeUIWidgets/Assets/Scripts/Equatable/equatable_mixin.cs
-
113AwesomeUIWidgets/Assets/Scripts/Equatable/equatable_utils.cs
-
20AwesomeUIWidgets/Assets/Scripts/Models/date_header.cs
-
25AwesomeUIWidgets/Assets/Scripts/Models/message_spacer.cs
-
27AwesomeUIWidgets/Assets/Scripts/Models/preview_image.cs
-
55AwesomeUIWidgets/Assets/Scripts/Widgets/attachment_button.cs
-
454AwesomeUIWidgets/Assets/Scripts/Widgets/chat.cs
-
33AwesomeUIWidgets/Assets/Scripts/Widgets/inherited_l10n.cs
-
33AwesomeUIWidgets/Assets/Scripts/Widgets/inherited_theme.cs
-
33AwesomeUIWidgets/Assets/Scripts/Widgets/inherited_user.cs
-
254AwesomeUIWidgets/Assets/Scripts/Widgets/input.cs
-
272AwesomeUIWidgets/Assets/Scripts/Widgets/message.cs
-
45AwesomeUIWidgets/Assets/Scripts/Widgets/send_button.cs
-
156AwesomeUIWidgets/Assets/Scripts/Widgets/text_message.cs
-
299AwesomeUIWidgets/Assets/Scripts/Widgets/chat_list.cs
-
4AwesomeUIWidgets/Assets/StreamingAssets/assets/icon-attachment.png
-
3AwesomeUIWidgets/Assets/StreamingAssets/assets/icon-seen.png
-
4AwesomeUIWidgets/Assets/StreamingAssets/assets/icon-send.png
|
|||
%YAML 1.1 |
|||
%TAG !u! tag:unity3d.com,2011: |
|||
--- !u!29 &1 |
|||
OcclusionCullingSettings: |
|||
m_ObjectHideFlags: 0 |
|||
serializedVersion: 2 |
|||
m_OcclusionBakeSettings: |
|||
smallestOccluder: 5 |
|||
smallestHole: 0.25 |
|||
backfaceThreshold: 100 |
|||
m_SceneGUID: 00000000000000000000000000000000 |
|||
m_OcclusionCullingData: {fileID: 0} |
|||
--- !u!104 &2 |
|||
RenderSettings: |
|||
m_ObjectHideFlags: 0 |
|||
serializedVersion: 9 |
|||
m_Fog: 0 |
|||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} |
|||
m_FogMode: 3 |
|||
m_FogDensity: 0.01 |
|||
m_LinearFogStart: 0 |
|||
m_LinearFogEnd: 300 |
|||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} |
|||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} |
|||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} |
|||
m_AmbientIntensity: 1 |
|||
m_AmbientMode: 0 |
|||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} |
|||
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} |
|||
m_HaloStrength: 0.5 |
|||
m_FlareStrength: 1 |
|||
m_FlareFadeSpeed: 3 |
|||
m_HaloTexture: {fileID: 0} |
|||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} |
|||
m_DefaultReflectionMode: 0 |
|||
m_DefaultReflectionResolution: 128 |
|||
m_ReflectionBounces: 1 |
|||
m_ReflectionIntensity: 1 |
|||
m_CustomReflection: {fileID: 0} |
|||
m_Sun: {fileID: 0} |
|||
m_IndirectSpecularColor: {r: 0.44657826, g: 0.49641263, b: 0.57481676, a: 1} |
|||
m_UseRadianceAmbientProbe: 0 |
|||
--- !u!157 &3 |
|||
LightmapSettings: |
|||
m_ObjectHideFlags: 0 |
|||
serializedVersion: 11 |
|||
m_GIWorkflowMode: 1 |
|||
m_GISettings: |
|||
serializedVersion: 2 |
|||
m_BounceScale: 1 |
|||
m_IndirectOutputScale: 1 |
|||
m_AlbedoBoost: 1 |
|||
m_EnvironmentLightingMode: 0 |
|||
m_EnableBakedLightmaps: 1 |
|||
m_EnableRealtimeLightmaps: 0 |
|||
m_LightmapEditorSettings: |
|||
serializedVersion: 12 |
|||
m_Resolution: 2 |
|||
m_BakeResolution: 40 |
|||
m_AtlasSize: 1024 |
|||
m_AO: 0 |
|||
m_AOMaxDistance: 1 |
|||
m_CompAOExponent: 1 |
|||
m_CompAOExponentDirect: 0 |
|||
m_ExtractAmbientOcclusion: 0 |
|||
m_Padding: 2 |
|||
m_LightmapParameters: {fileID: 0} |
|||
m_LightmapsBakeMode: 1 |
|||
m_TextureCompression: 1 |
|||
m_FinalGather: 0 |
|||
m_FinalGatherFiltering: 1 |
|||
m_FinalGatherRayCount: 256 |
|||
m_ReflectionCompression: 2 |
|||
m_MixedBakeMode: 2 |
|||
m_BakeBackend: 1 |
|||
m_PVRSampling: 1 |
|||
m_PVRDirectSampleCount: 32 |
|||
m_PVRSampleCount: 512 |
|||
m_PVRBounces: 2 |
|||
m_PVREnvironmentSampleCount: 256 |
|||
m_PVREnvironmentReferencePointCount: 2048 |
|||
m_PVRFilteringMode: 1 |
|||
m_PVRDenoiserTypeDirect: 1 |
|||
m_PVRDenoiserTypeIndirect: 1 |
|||
m_PVRDenoiserTypeAO: 1 |
|||
m_PVRFilterTypeDirect: 0 |
|||
m_PVRFilterTypeIndirect: 0 |
|||
m_PVRFilterTypeAO: 0 |
|||
m_PVREnvironmentMIS: 1 |
|||
m_PVRCulling: 1 |
|||
m_PVRFilteringGaussRadiusDirect: 1 |
|||
m_PVRFilteringGaussRadiusIndirect: 5 |
|||
m_PVRFilteringGaussRadiusAO: 2 |
|||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5 |
|||
m_PVRFilteringAtrousPositionSigmaIndirect: 2 |
|||
m_PVRFilteringAtrousPositionSigmaAO: 1 |
|||
m_ExportTrainingData: 0 |
|||
m_TrainingDataDestination: TrainingData |
|||
m_LightProbeSampleCountMultiplier: 4 |
|||
m_LightingDataAsset: {fileID: 0} |
|||
m_UseShadowmask: 1 |
|||
--- !u!196 &4 |
|||
NavMeshSettings: |
|||
serializedVersion: 2 |
|||
m_ObjectHideFlags: 0 |
|||
m_BuildSettings: |
|||
serializedVersion: 2 |
|||
agentTypeID: 0 |
|||
agentRadius: 0.5 |
|||
agentHeight: 2 |
|||
agentSlope: 45 |
|||
agentClimb: 0.4 |
|||
ledgeDropHeight: 0 |
|||
maxJumpAcrossDistance: 0 |
|||
minRegionArea: 2 |
|||
manualCellSize: 0 |
|||
cellSize: 0.16666667 |
|||
manualTileSize: 0 |
|||
tileSize: 256 |
|||
accuratePlacement: 0 |
|||
debug: |
|||
m_Flags: 0 |
|||
m_NavMeshData: {fileID: 0} |
|||
--- !u!1 &1125948832 |
|||
GameObject: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
serializedVersion: 6 |
|||
m_Component: |
|||
- component: {fileID: 1125948836} |
|||
- component: {fileID: 1125948835} |
|||
- component: {fileID: 1125948834} |
|||
- component: {fileID: 1125948833} |
|||
m_Layer: 0 |
|||
m_Name: Cube |
|||
m_TagString: Untagged |
|||
m_Icon: {fileID: 0} |
|||
m_NavMeshLayer: 0 |
|||
m_StaticEditorFlags: 0 |
|||
m_IsActive: 1 |
|||
--- !u!65 &1125948833 |
|||
BoxCollider: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1125948832} |
|||
m_Material: {fileID: 0} |
|||
m_IsTrigger: 0 |
|||
m_Enabled: 0 |
|||
serializedVersion: 2 |
|||
m_Size: {x: 1, y: 1, z: 1} |
|||
m_Center: {x: 0, y: 0, z: 0} |
|||
--- !u!23 &1125948834 |
|||
MeshRenderer: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1125948832} |
|||
m_Enabled: 0 |
|||
m_CastShadows: 1 |
|||
m_ReceiveShadows: 1 |
|||
m_DynamicOccludee: 1 |
|||
m_MotionVectors: 1 |
|||
m_LightProbeUsage: 1 |
|||
m_ReflectionProbeUsage: 1 |
|||
m_RayTracingMode: 2 |
|||
m_RenderingLayerMask: 1 |
|||
m_RendererPriority: 0 |
|||
m_Materials: |
|||
- {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} |
|||
m_StaticBatchInfo: |
|||
firstSubMesh: 0 |
|||
subMeshCount: 0 |
|||
m_StaticBatchRoot: {fileID: 0} |
|||
m_ProbeAnchor: {fileID: 0} |
|||
m_LightProbeVolumeOverride: {fileID: 0} |
|||
m_ScaleInLightmap: 1 |
|||
m_ReceiveGI: 1 |
|||
m_PreserveUVs: 0 |
|||
m_IgnoreNormalsForChartDetection: 0 |
|||
m_ImportantGI: 0 |
|||
m_StitchLightmapSeams: 1 |
|||
m_SelectedEditorRenderState: 3 |
|||
m_MinimumChartSize: 4 |
|||
m_AutoUVMaxDistance: 0.5 |
|||
m_AutoUVMaxAngle: 89 |
|||
m_LightmapParameters: {fileID: 0} |
|||
m_SortingLayerID: 0 |
|||
m_SortingLayer: 0 |
|||
m_SortingOrder: 0 |
|||
--- !u!33 &1125948835 |
|||
MeshFilter: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1125948832} |
|||
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} |
|||
--- !u!4 &1125948836 |
|||
Transform: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1125948832} |
|||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} |
|||
m_LocalPosition: {x: -80, y: -1.166, z: 229} |
|||
m_LocalScale: {x: 1, y: 1, z: 1} |
|||
m_Children: |
|||
- {fileID: 1315574209} |
|||
m_Father: {fileID: 0} |
|||
m_RootOrder: 3 |
|||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} |
|||
--- !u!1 &1252940034 |
|||
GameObject: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
serializedVersion: 6 |
|||
m_Component: |
|||
- component: {fileID: 1252940037} |
|||
- component: {fileID: 1252940036} |
|||
- component: {fileID: 1252940035} |
|||
m_Layer: 0 |
|||
m_Name: Main Camera |
|||
m_TagString: MainCamera |
|||
m_Icon: {fileID: 0} |
|||
m_NavMeshLayer: 0 |
|||
m_StaticEditorFlags: 0 |
|||
m_IsActive: 1 |
|||
--- !u!81 &1252940035 |
|||
AudioListener: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1252940034} |
|||
m_Enabled: 1 |
|||
--- !u!20 &1252940036 |
|||
Camera: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1252940034} |
|||
m_Enabled: 1 |
|||
serializedVersion: 2 |
|||
m_ClearFlags: 1 |
|||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} |
|||
m_projectionMatrixMode: 1 |
|||
m_GateFitMode: 2 |
|||
m_FOVAxisMode: 0 |
|||
m_SensorSize: {x: 36, y: 24} |
|||
m_LensShift: {x: 0, y: 0} |
|||
m_FocalLength: 50 |
|||
m_NormalizedViewPortRect: |
|||
serializedVersion: 2 |
|||
x: 0 |
|||
y: 0 |
|||
width: 1 |
|||
height: 1 |
|||
near clip plane: 0.3 |
|||
far clip plane: 1000 |
|||
field of view: 60 |
|||
orthographic: 0 |
|||
orthographic size: 5 |
|||
m_Depth: -1 |
|||
m_CullingMask: |
|||
serializedVersion: 2 |
|||
m_Bits: 4294967295 |
|||
m_RenderingPath: -1 |
|||
m_TargetTexture: {fileID: 0} |
|||
m_TargetDisplay: 0 |
|||
m_TargetEye: 3 |
|||
m_HDR: 1 |
|||
m_AllowMSAA: 1 |
|||
m_AllowDynamicResolution: 0 |
|||
m_ForceIntoRT: 0 |
|||
m_OcclusionCulling: 1 |
|||
m_StereoConvergence: 10 |
|||
m_StereoSeparation: 0.022 |
|||
--- !u!4 &1252940037 |
|||
Transform: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1252940034} |
|||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} |
|||
m_LocalPosition: {x: 0, y: 1, z: -10} |
|||
m_LocalScale: {x: 1, y: 1, z: 1} |
|||
m_Children: [] |
|||
m_Father: {fileID: 0} |
|||
m_RootOrder: 0 |
|||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} |
|||
--- !u!1 &1315574208 |
|||
GameObject: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
serializedVersion: 6 |
|||
m_Component: |
|||
- component: {fileID: 1315574209} |
|||
- component: {fileID: 1315574212} |
|||
- component: {fileID: 1315574211} |
|||
- component: {fileID: 1315574210} |
|||
m_Layer: 5 |
|||
m_Name: Canvas |
|||
m_TagString: Untagged |
|||
m_Icon: {fileID: 0} |
|||
m_NavMeshLayer: 0 |
|||
m_StaticEditorFlags: 0 |
|||
m_IsActive: 1 |
|||
--- !u!224 &1315574209 |
|||
RectTransform: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1315574208} |
|||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} |
|||
m_LocalPosition: {x: 0, y: 0, z: 232} |
|||
m_LocalScale: {x: 1, y: 1, z: 1} |
|||
m_Children: |
|||
- {fileID: 1558042840} |
|||
m_Father: {fileID: 1125948836} |
|||
m_RootOrder: 0 |
|||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} |
|||
m_AnchorMin: {x: 0, y: 0} |
|||
m_AnchorMax: {x: 0, y: 0} |
|||
m_AnchoredPosition: {x: -46, y: 152} |
|||
m_SizeDelta: {x: 398, y: 784} |
|||
m_Pivot: {x: 0.5, y: 0.5} |
|||
--- !u!114 &1315574210 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1315574208} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
m_IgnoreReversedGraphics: 1 |
|||
m_BlockingObjects: 0 |
|||
m_BlockingMask: |
|||
serializedVersion: 2 |
|||
m_Bits: 4294967295 |
|||
--- !u!114 &1315574211 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1315574208} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
m_UiScaleMode: 0 |
|||
m_ReferencePixelsPerUnit: 100 |
|||
m_ScaleFactor: 1 |
|||
m_ReferenceResolution: {x: 800, y: 600} |
|||
m_ScreenMatchMode: 0 |
|||
m_MatchWidthOrHeight: 0 |
|||
m_PhysicalUnit: 3 |
|||
m_FallbackScreenDPI: 96 |
|||
m_DefaultSpriteDPI: 96 |
|||
m_DynamicPixelsPerUnit: 1 |
|||
--- !u!223 &1315574212 |
|||
Canvas: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1315574208} |
|||
m_Enabled: 1 |
|||
serializedVersion: 3 |
|||
m_RenderMode: 2 |
|||
m_Camera: {fileID: 0} |
|||
m_PlaneDistance: 100 |
|||
m_PixelPerfect: 0 |
|||
m_ReceivesEvents: 1 |
|||
m_OverrideSorting: 0 |
|||
m_OverridePixelPerfect: 0 |
|||
m_SortingBucketNormalizedSize: 0 |
|||
m_AdditionalShaderChannelsFlag: 0 |
|||
m_SortingLayerID: 0 |
|||
m_SortingOrder: 0 |
|||
m_TargetDisplay: 0 |
|||
--- !u!1 &1511805122 |
|||
GameObject: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
serializedVersion: 6 |
|||
m_Component: |
|||
- component: {fileID: 1511805125} |
|||
- component: {fileID: 1511805124} |
|||
- component: {fileID: 1511805123} |
|||
m_Layer: 0 |
|||
m_Name: EventSystem |
|||
m_TagString: Untagged |
|||
m_Icon: {fileID: 0} |
|||
m_NavMeshLayer: 0 |
|||
m_StaticEditorFlags: 0 |
|||
m_IsActive: 1 |
|||
--- !u!114 &1511805123 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1511805122} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
m_HorizontalAxis: Horizontal |
|||
m_VerticalAxis: Vertical |
|||
m_SubmitButton: Submit |
|||
m_CancelButton: Cancel |
|||
m_InputActionsPerSecond: 10 |
|||
m_RepeatDelay: 0.5 |
|||
m_ForceModuleActive: 0 |
|||
--- !u!114 &1511805124 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1511805122} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
m_FirstSelected: {fileID: 0} |
|||
m_sendNavigationEvents: 1 |
|||
m_DragThreshold: 10 |
|||
--- !u!4 &1511805125 |
|||
Transform: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1511805122} |
|||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} |
|||
m_LocalPosition: {x: 0, y: 0, z: 0} |
|||
m_LocalScale: {x: 1, y: 1, z: 1} |
|||
m_Children: [] |
|||
m_Father: {fileID: 0} |
|||
m_RootOrder: 2 |
|||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} |
|||
--- !u!1 &1542210717 |
|||
GameObject: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
serializedVersion: 6 |
|||
m_Component: |
|||
- component: {fileID: 1542210719} |
|||
- component: {fileID: 1542210718} |
|||
m_Layer: 0 |
|||
m_Name: Directional Light |
|||
m_TagString: Untagged |
|||
m_Icon: {fileID: 0} |
|||
m_NavMeshLayer: 0 |
|||
m_StaticEditorFlags: 0 |
|||
m_IsActive: 1 |
|||
--- !u!108 &1542210718 |
|||
Light: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1542210717} |
|||
m_Enabled: 1 |
|||
serializedVersion: 10 |
|||
m_Type: 1 |
|||
m_Shape: 0 |
|||
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} |
|||
m_Intensity: 1 |
|||
m_Range: 10 |
|||
m_SpotAngle: 30 |
|||
m_InnerSpotAngle: 21.80208 |
|||
m_CookieSize: 10 |
|||
m_Shadows: |
|||
m_Type: 2 |
|||
m_Resolution: -1 |
|||
m_CustomResolution: -1 |
|||
m_Strength: 1 |
|||
m_Bias: 0.05 |
|||
m_NormalBias: 0.4 |
|||
m_NearPlane: 0.2 |
|||
m_CullingMatrixOverride: |
|||
e00: 1 |
|||
e01: 0 |
|||
e02: 0 |
|||
e03: 0 |
|||
e10: 0 |
|||
e11: 1 |
|||
e12: 0 |
|||
e13: 0 |
|||
e20: 0 |
|||
e21: 0 |
|||
e22: 1 |
|||
e23: 0 |
|||
e30: 0 |
|||
e31: 0 |
|||
e32: 0 |
|||
e33: 1 |
|||
m_UseCullingMatrixOverride: 0 |
|||
m_Cookie: {fileID: 0} |
|||
m_DrawHalo: 0 |
|||
m_Flare: {fileID: 0} |
|||
m_RenderMode: 0 |
|||
m_CullingMask: |
|||
serializedVersion: 2 |
|||
m_Bits: 4294967295 |
|||
m_RenderingLayerMask: 1 |
|||
m_Lightmapping: 4 |
|||
m_LightShadowCasterMode: 0 |
|||
m_AreaSize: {x: 1, y: 1} |
|||
m_BounceIntensity: 1 |
|||
m_ColorTemperature: 6570 |
|||
m_UseColorTemperature: 0 |
|||
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} |
|||
m_UseBoundingSphereOverride: 0 |
|||
m_ShadowRadius: 0 |
|||
m_ShadowAngle: 0 |
|||
--- !u!4 &1542210719 |
|||
Transform: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1542210717} |
|||
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} |
|||
m_LocalPosition: {x: 0, y: 3, z: 0} |
|||
m_LocalScale: {x: 1, y: 1, z: 1} |
|||
m_Children: [] |
|||
m_Father: {fileID: 0} |
|||
m_RootOrder: 1 |
|||
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} |
|||
--- !u!1 &1558042839 |
|||
GameObject: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
serializedVersion: 6 |
|||
m_Component: |
|||
- component: {fileID: 1558042840} |
|||
- component: {fileID: 1558042842} |
|||
- component: {fileID: 1558042841} |
|||
m_Layer: 5 |
|||
m_Name: RawImage |
|||
m_TagString: Untagged |
|||
m_Icon: {fileID: 0} |
|||
m_NavMeshLayer: 0 |
|||
m_StaticEditorFlags: 0 |
|||
m_IsActive: 1 |
|||
--- !u!224 &1558042840 |
|||
RectTransform: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1558042839} |
|||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} |
|||
m_LocalPosition: {x: 0, y: 0, z: -125} |
|||
m_LocalScale: {x: 1, y: 1, z: 1} |
|||
m_Children: [] |
|||
m_Father: {fileID: 1315574209} |
|||
m_RootOrder: 0 |
|||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} |
|||
m_AnchorMin: {x: 0.5, y: 0.5} |
|||
m_AnchorMax: {x: 0.5, y: 0.5} |
|||
m_AnchoredPosition: {x: 169, y: -153} |
|||
m_SizeDelta: {x: 300, y: 400} |
|||
m_Pivot: {x: 0.5, y: 0.5} |
|||
--- !u!114 &1558042841 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1558042839} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: 95cfb90e363891b40b68575b9bf6b7dc, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
m_Material: {fileID: 0} |
|||
m_Color: {r: 1, g: 1, b: 1, a: 1} |
|||
m_RaycastTarget: 1 |
|||
m_Maskable: 1 |
|||
m_OnCullStateChanged: |
|||
m_PersistentCalls: |
|||
m_Calls: [] |
|||
m_Texture: {fileID: 1916630145} |
|||
m_UVRect: |
|||
serializedVersion: 2 |
|||
x: 0 |
|||
y: 0 |
|||
width: 1 |
|||
height: 1 |
|||
hardwareAntiAliasing: 0 |
|||
fonts: [] |
|||
--- !u!222 &1558042842 |
|||
CanvasRenderer: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1558042839} |
|||
m_CullTransparentMesh: 0 |
|||
--- !u!28 &1916630145 |
|||
Texture2D: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_Name: |
|||
m_ImageContentsHash: |
|||
serializedVersion: 2 |
|||
Hash: 00000000000000000000000000000000 |
|||
m_ForcedFallbackFormat: 4 |
|||
m_DownscaleFallback: 0 |
|||
serializedVersion: 3 |
|||
m_Width: 0 |
|||
m_Height: 0 |
|||
m_CompleteImageSize: 0 |
|||
m_TextureFormat: 0 |
|||
m_MipCount: 1 |
|||
m_IsReadable: 1 |
|||
m_IgnoreMasterTextureLimit: 0 |
|||
m_IsPreProcessed: 0 |
|||
m_StreamingMipmaps: 0 |
|||
m_StreamingMipmapsPriority: 0 |
|||
m_AlphaIsTransparency: 0 |
|||
m_ImageCount: 0 |
|||
m_TextureDimension: 2 |
|||
m_TextureSettings: |
|||
serializedVersion: 2 |
|||
m_FilterMode: 1 |
|||
m_Aniso: 1 |
|||
m_MipBias: 0 |
|||
m_WrapU: 0 |
|||
m_WrapV: 0 |
|||
m_WrapW: 0 |
|||
m_LightmapFormat: 0 |
|||
m_ColorSpace: 0 |
|||
image data: 0 |
|||
_typelessdata: |
|||
m_StreamData: |
|||
offset: 0 |
|||
size: 0 |
|||
path: |
|||
m_OriginalWidth: 0 |
|||
m_OriginalHeight: 0 |
|||
m_OriginalAssetGuid: 00000000000000000000000000000000 |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using ChatComponents; |
|||
using uiwidgets; |
|||
using Unity.UIWidgets.async; |
|||
using Unity.UIWidgets.cupertino; |
|||
using Unity.UIWidgets.engine; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.widgets; |
|||
using UnityEditor.Build.Content; |
|||
using UnityEngine; |
|||
using Random = System.Random; |
|||
using TextStyle = Unity.UIWidgets.ui.TextStyle; |
|||
|
|||
namespace UIWidgetsSample |
|||
|
|||
{ |
|||
public class ChatRoomDemo : UIWidgetsPanel |
|||
{ |
|||
protected void OnEnable() |
|||
{ |
|||
base.OnEnable(); |
|||
} |
|||
|
|||
protected override void main() |
|||
{ |
|||
ui_.runApp(new MyApp()); |
|||
} |
|||
|
|||
class MyApp : StatelessWidget |
|||
{ |
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
return new CupertinoApp( |
|||
|
|||
home: new DateAndTimePickerWidget() |
|||
); |
|||
} |
|||
} |
|||
} |
|||
[System.Serializable] |
|||
public class ChatMessages |
|||
{ |
|||
public List<ChatMessage> chatMessages; |
|||
} |
|||
|
|||
[System.Serializable] |
|||
public class ChatMessage |
|||
{ |
|||
public ChatUser author; |
|||
public int createdAt; |
|||
public string id; |
|||
public string status; |
|||
public string text; |
|||
public string type; |
|||
|
|||
} |
|||
|
|||
[System.Serializable] |
|||
public class ChatUser |
|||
{ |
|||
|
|||
public string firstName; |
|||
public string id; |
|||
public string imageUrl; |
|||
|
|||
} |
|||
|
|||
|
|||
|
|||
public class ChatPage : StatefulWidget |
|||
{ |
|||
public ChatPage(Key key = null) : base(key) |
|||
{ |
|||
} |
|||
|
|||
public override State createState() |
|||
{ |
|||
return new _ChatPageState(); |
|||
} |
|||
} |
|||
|
|||
public class _ChatPageState : State<ChatPage> |
|||
{ |
|||
public readonly ChatComponents.User _user = new ChatComponents.User("06c33e8b-e835-4736-80f4-63f44b66666c"); |
|||
private List<ChatComponents.Message> _messages = new List<ChatComponents.Message>(); |
|||
public int _page = 0; |
|||
|
|||
public override void initState() |
|||
{ |
|||
base.initState(); |
|||
_loadMessages(); |
|||
} |
|||
|
|||
private void _addMessage(ChatComponents.Message message) |
|||
{ |
|||
setState(() => |
|||
{ |
|||
_messages.Insert(0, message); |
|||
}); |
|||
} |
|||
|
|||
private void _handleAtachmentPressed() |
|||
{ |
|||
material_.showModalBottomSheet<object>( |
|||
context, |
|||
context => |
|||
{ |
|||
return new SizedBox( |
|||
height: 144, |
|||
child: new Column( |
|||
crossAxisAlignment: CrossAxisAlignment.stretch, |
|||
children: new List<Widget> |
|||
{ |
|||
new CupertinoButton( |
|||
onPressed: () => |
|||
{ |
|||
Navigator.pop(context); |
|||
//_handleImageSelection();
|
|||
}, |
|||
child: new Align( |
|||
alignment: Alignment.centerLeft, |
|||
child: new Text("Photo") |
|||
) |
|||
), |
|||
new CupertinoButton( |
|||
onPressed: () => |
|||
{ |
|||
Navigator.pop(context); |
|||
//_handleFileSelection();
|
|||
}, |
|||
child: |
|||
new Align( |
|||
alignment: Alignment.centerLeft, |
|||
child: new Text("File") |
|||
) |
|||
), |
|||
new CupertinoButton( |
|||
onPressed: () => Navigator.pop(context), |
|||
child: new Align( |
|||
alignment: Alignment.centerLeft, |
|||
child: new Text("Cancel") |
|||
) |
|||
) |
|||
} |
|||
) |
|||
); |
|||
} |
|||
); |
|||
} |
|||
|
|||
/* private void _handleFileSelection() |
|||
{ |
|||
var result = FilePicker.platform.pickFiles( |
|||
type: FileType.any |
|||
); |
|||
|
|||
if (result != null) |
|||
{ |
|||
var message = new ChatComponents.FileMessage( |
|||
_user, |
|||
createdAt: DateTime.Now.Millisecond, |
|||
name: result.files.single.name, |
|||
id: Uuid().v4(), |
|||
mimeType: |
|||
lookupMimeType(result.files.single.path ?? ""), |
|||
size: |
|||
result.files.single.size, |
|||
uri: |
|||
result.files.single.path ?? "" |
|||
); |
|||
|
|||
_addMessage(message); |
|||
} |
|||
}*/ |
|||
|
|||
/* private void _handleImageSelection() |
|||
{ |
|||
var result = new ImagePicker().getImage( |
|||
imageQuality: 70, |
|||
maxWidth: 1440, |
|||
source: ImageSource.gallery |
|||
); |
|||
|
|||
if (result != null) |
|||
{ |
|||
var bytes = result.readAsBytes(); |
|||
var image = decodeImageFromList(bytes); |
|||
var name = result.path.split("/").last; |
|||
|
|||
var message = new ChatComponents.ImageMessage( |
|||
_user, |
|||
createdAt: DateTime.Now.Millisecond, |
|||
height: image.height.toDouble(), |
|||
id: Uuid().v4(), |
|||
name: |
|||
name, |
|||
size: |
|||
bytes.length, |
|||
uri: |
|||
result.path, |
|||
width: |
|||
image.width.toDouble() |
|||
); |
|||
|
|||
_addMessage(message); |
|||
} |
|||
}*/ |
|||
|
|||
private void _handleMessageTap(ChatComponents.Message message) |
|||
{ |
|||
if (message is ChatComponents.FileMessage) |
|||
//OpenFile.open(message.uri);
|
|||
Debug.Log("OPEN FILE"); |
|||
} |
|||
|
|||
private void _handlePreviewDataFetched( |
|||
ChatComponents.TextMessage message, |
|||
ChatComponents.PreviewData previewData |
|||
) |
|||
{ |
|||
var index = 0; |
|||
foreach (var element in _messages) |
|||
{ |
|||
if (element.id == message.id) |
|||
{ |
|||
index = _messages.IndexOf(element); |
|||
} |
|||
} |
|||
|
|||
|
|||
var updatedMessage = _messages[index].copyWith(previewData: previewData); |
|||
|
|||
WidgetsBinding.instance?.addPostFrameCallback(_ => |
|||
{ |
|||
setState(() => { _messages[index] = updatedMessage; }); |
|||
}); |
|||
} |
|||
|
|||
private void _handleSendPressed(ChatComponents.PartialText message) |
|||
{ |
|||
var textMessage = new ChatComponents.TextMessage( |
|||
_user, |
|||
createdAt: DateTime.Now.Millisecond, |
|||
id: "b4878b96-efbc-479a-8291-474ef323aa" + _messages.Count.ToString(), |
|||
text: message.text |
|||
); |
|||
|
|||
_addMessage(textMessage); |
|||
} |
|||
private Future _handleEndReached() |
|||
{ |
|||
List<ChatComponents.TextMessage> messages = new List<ChatComponents.TextMessage>(); |
|||
List<string> ids = new List<string>() { }; |
|||
for (int i = 1; i < 10; i++) |
|||
{ |
|||
ids.Add(i.ToString()); |
|||
} |
|||
foreach (var id in ids) |
|||
{ |
|||
messages.Add(new ChatComponents.TextMessage( |
|||
_user, |
|||
id: "b4878b96-efbc-479a-8291-474ef323aa" + id, |
|||
text: id + "......" |
|||
) ); |
|||
} |
|||
setState(()=> { |
|||
{ |
|||
_messages.AddRange(messages); |
|||
_page = _page + 1; |
|||
} |
|||
}); |
|||
return Future.value(); |
|||
} |
|||
private void _loadMessages() |
|||
{ |
|||
List<ChatComponents.Message> results = new List<ChatComponents.Message>(); |
|||
TextAsset info = Resources.Load<TextAsset>("assets/messages"); |
|||
|
|||
List<ChatMessage> chatMessages = new List<ChatMessage>(); |
|||
List<string> jsoninfo = info.text.Split('&').ToList(); |
|||
foreach (var _info in jsoninfo) |
|||
{ |
|||
var _message = JsonUtility.FromJson<ChatMessage>(_info); |
|||
chatMessages.Add(_message); |
|||
} |
|||
foreach (var _message in chatMessages) |
|||
{ |
|||
|
|||
results.Add( new ChatComponents.TextMessage( |
|||
author: new User( |
|||
id: _message.author.id, |
|||
firstName: _message.author.firstName, |
|||
imageUrl: _message.author.imageUrl |
|||
), |
|||
createdAt: _message.createdAt, |
|||
id: _message.id, |
|||
status: ChatRoomUtils.getStatusFromString(_message.status), |
|||
text: _message.text |
|||
) |
|||
); |
|||
} |
|||
|
|||
setState(() => { _messages = results; }); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
|
|||
return new Container( |
|||
child: new Chat( |
|||
messages: _messages, |
|||
onAttachmentPressed: _handleAtachmentPressed, |
|||
onMessageTap: _handleMessageTap, |
|||
onPreviewDataFetched: ( |
|||
previewData, |
|||
message)=> |
|||
{ |
|||
_handlePreviewDataFetched(message,previewData); |
|||
}, |
|||
onTextChanged : (_str) => { }, |
|||
onSendPressed: _handleSendPressed, |
|||
onEndReached: _handleEndReached, |
|||
//onEndReachedThreshold: 0.65f,
|
|||
user: _user |
|||
) |
|||
|
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using uiwidgets; |
|||
using Unity.UIWidgets.cupertino; |
|||
using Unity.UIWidgets.engine; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using UnityEngine; |
|||
using Color = Unity.UIWidgets.ui.Color; |
|||
using Text = Unity.UIWidgets.widgets.Text; |
|||
using ui_ = Unity.UIWidgets.widgets.ui_; |
|||
using TextStyle = Unity.UIWidgets.painting.TextStyle; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
|
|||
|
|||
public class DateAndTimePicker : UIWidgetsPanel |
|||
{ |
|||
protected void OnEnable() |
|||
{ |
|||
base.OnEnable(); |
|||
} |
|||
|
|||
protected override void main() |
|||
{ |
|||
ui_.runApp(new MyApp()); |
|||
} |
|||
|
|||
class MyApp : StatelessWidget |
|||
{ |
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
return new CupertinoApp( |
|||
home: new DateAndTimePickerWidget() |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public class DateAndTimePickerWidget : StatefulWidget { |
|||
|
|||
public override State createState() { |
|||
return new DateAndTimePickerWidgetState(); |
|||
} |
|||
} |
|||
|
|||
|
|||
public class DateAndTimePickerWidgetState : State<DateAndTimePickerWidget> |
|||
{ |
|||
|
|||
Widget _buildMenu(List<Widget> children) { |
|||
return new Container( |
|||
decoration: new BoxDecoration( |
|||
color: CupertinoTheme.of(this.context).scaffoldBackgroundColor, |
|||
border: new Border( |
|||
top: new BorderSide(color: new Color(0xFFBCBBC1), width: 0.0f), |
|||
bottom: new BorderSide(color: new Color(0xFFBCBBC1), width: 0.0f) |
|||
) |
|||
), |
|||
height: 44.0f, |
|||
child: new Padding( |
|||
padding: EdgeInsets.symmetric(horizontal: 16.0f), |
|||
child: new SafeArea( |
|||
top: false, |
|||
bottom: false, |
|||
child: new Row( |
|||
//mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|||
children: children |
|||
) |
|||
) |
|||
) |
|||
); |
|||
} |
|||
DateTime dateTime = DateTime.Now; |
|||
|
|||
Widget _buildBottomPicker(Widget picker) { |
|||
return new Container( |
|||
height: 216f, |
|||
width : 500f, |
|||
padding: EdgeInsets.only(top: 6.0f), |
|||
color: Colors.white, |
|||
child: new DefaultTextStyle( |
|||
style: new TextStyle( |
|||
color: Colors.red, |
|||
fontSize: 12.0f |
|||
), |
|||
child: new GestureDetector( |
|||
// Blocks taps from propagating to the modal sheet and popping.
|
|||
onTap: () => { }, |
|||
child: new SafeArea( |
|||
top: false, |
|||
child: picker |
|||
) |
|||
) |
|||
) |
|||
); |
|||
} |
|||
Widget _buildDateAndTimePicker(BuildContext context) { |
|||
return new GestureDetector( |
|||
onTap: () => { |
|||
CupertinoRouteUtils.showCupertinoModalPopup( |
|||
context: context, |
|||
builder: (BuildContext _context) => { |
|||
return this._buildBottomPicker( |
|||
new CupertinoTheme( |
|||
data: new CupertinoThemeData( |
|||
textTheme: new CupertinoTextThemeData( |
|||
dateTimePickerTextStyle: new TextStyle( |
|||
fontSize: 16, |
|||
color: Colors.white |
|||
) |
|||
) |
|||
), |
|||
child :new CupertinoDatePicker( |
|||
backgroundColor: Colors.black, |
|||
mode: CupertinoDatePickerMode.dateAndTime, |
|||
initialDateTime: this.dateTime, |
|||
onDateTimeChanged: (DateTime newDateTime) => { |
|||
this.setState(() => this.dateTime = newDateTime); |
|||
} |
|||
) |
|||
)); |
|||
} |
|||
); |
|||
}, |
|||
child: new Text( |
|||
this.dateTime.ToString("MMMM dd, yyyy h:mm tt"), |
|||
style: new TextStyle(color: Colors.white) |
|||
|
|||
) |
|||
); |
|||
} |
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
return new Container( |
|||
color : Colors.black, |
|||
child : this._buildDateAndTimePicker(context) |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using UnityEngine; |
|||
using System.Collections; |
|||
|
|||
namespace UIWidgetsSample.Utils |
|||
{ |
|||
|
|||
public class FlyCamera : MonoBehaviour { |
|||
/* |
|||
Writen by Windexglow 11-13-10. Use it, edit it, steal it I don't care. |
|||
Converted to C# 27-02-13 - no credit wanted. |
|||
Simple flycam I made, since I couldn't find any others made public. |
|||
Made simple to use (drag and drop, done) for regular keyboard layout |
|||
wasd : basic movement |
|||
shift : Makes camera accelerate |
|||
space : Moves camera on X and Z axis only. So camera doesn't gain any height*/ |
|||
|
|||
|
|||
float mainSpeed = 10.0f; //regular speed
|
|||
float shiftAdd = 25.0f; //multiplied by how long shift is held. Basically running
|
|||
float maxShift = 100.0f; //Maximum speed when holdin gshift
|
|||
float camSens = 0.025f; //How sensitive it with mouse
|
|||
private Vector3 lastMouse = new Vector3(255, 255, 255); //kind of in the middle of the screen, rather than at the top (play)
|
|||
private float totalRun= 1.0f; |
|||
|
|||
void Update () { |
|||
lastMouse = UnityEngine.Input.mousePosition - lastMouse ; |
|||
lastMouse = new Vector3(-lastMouse.y * camSens, lastMouse.x * camSens, 0 ); |
|||
lastMouse = new Vector3(transform.eulerAngles.x + lastMouse.x , transform.eulerAngles.y + lastMouse.y, 0); |
|||
transform.eulerAngles = lastMouse; |
|||
lastMouse = UnityEngine.Input.mousePosition; |
|||
//Mouse camera angle done.
|
|||
|
|||
//Keyboard commands
|
|||
float f = 0.0f; |
|||
Vector3 p = GetBaseInput(); |
|||
if (p.sqrMagnitude > 0){ // only move while a direction key is pressed
|
|||
if (UnityEngine.Input.GetKey (KeyCode.LeftShift)){ |
|||
totalRun += Time.deltaTime; |
|||
p = p * totalRun * shiftAdd; |
|||
p.x = Mathf.Clamp(p.x, -maxShift, maxShift); |
|||
p.y = Mathf.Clamp(p.y, -maxShift, maxShift); |
|||
p.z = Mathf.Clamp(p.z, -maxShift, maxShift); |
|||
} else { |
|||
totalRun = Mathf.Clamp(totalRun * 0.5f, 1f, 1000f); |
|||
p = p * mainSpeed; |
|||
} |
|||
|
|||
p = p * Time.deltaTime; |
|||
Vector3 newPosition = transform.position; |
|||
transform.Translate(p); |
|||
newPosition.x = transform.position.x; |
|||
newPosition.z = transform.position.z; |
|||
transform.position = newPosition; |
|||
} |
|||
} |
|||
|
|||
private Vector3 GetBaseInput() { //returns the basic values, if it's 0 than it's not active.
|
|||
Vector3 p_Velocity = new Vector3(); |
|||
if (UnityEngine.Input.GetKey (KeyCode.W)){ |
|||
p_Velocity += new Vector3(0, 0 , 1); |
|||
} |
|||
if (UnityEngine.Input.GetKey (KeyCode.S)){ |
|||
p_Velocity += new Vector3(0, 0, -1); |
|||
} |
|||
if (UnityEngine.Input.GetKey (KeyCode.A)){ |
|||
p_Velocity += new Vector3(-1, 0, 0); |
|||
} |
|||
if (UnityEngine.Input.GetKey (KeyCode.D)){ |
|||
p_Velocity += new Vector3(1, 0, 0); |
|||
} |
|||
return p_Velocity; |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using Unity.UIWidgets.foundation; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public abstract class ChatL10n |
|||
{ |
|||
/// Accessibility label (hint) for the attachment button
|
|||
public readonly string attachmentButtonAccessibilityLabel; |
|||
|
|||
/// Placeholder when there are no messages
|
|||
public readonly string emptyChatPlaceholder; |
|||
|
|||
/// Accessibility label (hint) for the tap action on file message
|
|||
public readonly string fileButtonAccessibilityLabel; |
|||
|
|||
/// Placeholder for the text field
|
|||
public readonly string inputPlaceholder; |
|||
|
|||
/// Accessibility label (hint) for the send button
|
|||
public readonly string sendButtonAccessibilityLabel; |
|||
|
|||
/// Creates a new chat l10n based on provided copy
|
|||
public ChatL10n( |
|||
string attachmentButtonAccessibilityLabel = null, |
|||
string emptyChatPlaceholder = null, |
|||
string fileButtonAccessibilityLabel = null, |
|||
string inputPlaceholder = null, |
|||
string sendButtonAccessibilityLabel = null |
|||
) |
|||
{ |
|||
this.inputPlaceholder = inputPlaceholder; |
|||
this.attachmentButtonAccessibilityLabel = attachmentButtonAccessibilityLabel; |
|||
this.emptyChatPlaceholder = emptyChatPlaceholder; |
|||
this.fileButtonAccessibilityLabel = fileButtonAccessibilityLabel; |
|||
this.sendButtonAccessibilityLabel = sendButtonAccessibilityLabel; |
|||
} |
|||
} |
|||
|
|||
/// English l10n which extends [ChatL10n]
|
|||
public class ChatL10nEn : ChatL10n |
|||
{ |
|||
/// Creates English l10n. Use this constructor if you want to
|
|||
/// override only a couple of variables, otherwise create a new class
|
|||
/// which extends [ChatL10n]
|
|||
public ChatL10nEn( |
|||
string attachmentButtonAccessibilityLabel = "Send media", |
|||
string emptyChatPlaceholder = "No messages here yet", |
|||
string fileButtonAccessibilityLabel = "File", |
|||
string inputPlaceholder = "Message", |
|||
string sendButtonAccessibilityLabel = "Send" |
|||
) : base( |
|||
attachmentButtonAccessibilityLabel, |
|||
emptyChatPlaceholder, |
|||
fileButtonAccessibilityLabel, |
|||
inputPlaceholder, |
|||
sendButtonAccessibilityLabel |
|||
) |
|||
{ |
|||
} |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using TextStyle =Unity.UIWidgets.painting.TextStyle; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public class ChatThemeUtils |
|||
{ |
|||
public static List<Color> COLORS = new List<Color> |
|||
{ |
|||
new Color(0xffff6767), |
|||
new Color(0xff66e0da), |
|||
new Color(0xfff5a2d9), |
|||
new Color(0xfff0c722), |
|||
new Color(0xff6a85e5), |
|||
new Color(0xfffd9a6f), |
|||
new Color(0xff92db6e), |
|||
new Color(0xff73b8e5), |
|||
new Color(0xfffd7590), |
|||
new Color(0xffc78ae5) |
|||
}; |
|||
|
|||
/// Dark
|
|||
public static Color DARK = new Color(0xff1f1c38); |
|||
|
|||
/// Error
|
|||
public static Color ERROR = new Color(0xffff6767); |
|||
|
|||
/// N0
|
|||
public static Color NEUTRAL_0 = new Color(0xff1d1c21); |
|||
|
|||
/// N2
|
|||
public static Color NEUTRAL_2 = new Color(0xff9e9cab); |
|||
|
|||
/// N7
|
|||
public static Color NEUTRAL_7 = new Color(0xffffffff); |
|||
|
|||
/// N7 with opacity
|
|||
public static Color NEUTRAL_7_WITH_OPACITY = new Color(0x80ffffff); |
|||
|
|||
/// Primary
|
|||
public static Color PRIMARY = new Color(0xff6f61e8); |
|||
|
|||
/// Secondary
|
|||
public static Color SECONDARY = new Color(0xfff5f5f7); |
|||
|
|||
/// Secondary dark
|
|||
public static Color SECONDARY_DARK = new Color(0xff2b2250); |
|||
|
|||
public static float ratio = 0.5f; |
|||
} |
|||
|
|||
/// Base chat theme containing all required variables to make a theme.
|
|||
/// Extend this class if you want to create a custom theme.
|
|||
public abstract class ChatTheme |
|||
{ |
|||
/// Icon for select attachment button
|
|||
public readonly Widget attachmentButtonIcon; |
|||
|
|||
/// Used as a background color of a chat widget
|
|||
public readonly Color backgroundColor; |
|||
|
|||
/// Text style of the date dividers
|
|||
public readonly Unity.UIWidgets.painting.TextStyle dateDividerTextStyle; |
|||
|
|||
/// Icon for message's `delivered` status. For the best look use size of 16.
|
|||
public readonly Widget deliveredIcon; |
|||
|
|||
/// Icon inside file message
|
|||
public readonly Widget documentIcon; |
|||
|
|||
/// Text style of the empty chat placeholder
|
|||
public readonly TextStyle emptyChatPlaceholderTextStyle; |
|||
|
|||
/// Color to indicate something bad happened (usually - shades of red)
|
|||
public readonly Color errorColor; |
|||
|
|||
/// Icon for message's `error` status. For the best look use size of 16.
|
|||
public readonly Widget errorIcon; |
|||
|
|||
/// Color of the bottom bar where text field is
|
|||
public readonly Color inputBackgroundColor; |
|||
|
|||
/// Top border radius of the bottom bar where text field is
|
|||
public readonly BorderRadius inputBorderRadius; |
|||
|
|||
/// Color of the text field's text and attachment/send buttons
|
|||
public readonly Color inputTextColor; |
|||
|
|||
/// Text style of the message input. To change the color use [inputTextColor].
|
|||
public readonly TextStyle inputTextStyle; |
|||
|
|||
/// Border radius of message container
|
|||
public readonly float messageBorderRadius; |
|||
|
|||
/// Primary color of the chat used as a background of sent messages
|
|||
/// and statuses
|
|||
public readonly Color primaryColor; |
|||
|
|||
/// Body text style used for displaying text on different types
|
|||
/// of received messages
|
|||
public readonly TextStyle receivedMessageBodyTextStyle; |
|||
|
|||
/// Caption text style used for displaying secondary info (e.g. file size)
|
|||
/// on different types of received messages
|
|||
public readonly TextStyle receivedMessageCaptionTextStyle; |
|||
|
|||
/// Color of the document icon on received messages. Has no effect when
|
|||
/// [documentIcon] is used.
|
|||
public readonly Color receivedMessageDocumentIconColor; |
|||
|
|||
/// Text style used for displaying link description on received messages
|
|||
public readonly TextStyle receivedMessageLinkDescriptionTextStyle; |
|||
|
|||
/// Text style used for displaying link title on received messages
|
|||
public readonly TextStyle receivedMessageLinkTitleTextStyle; |
|||
|
|||
/// Secondary color, used as a background of received messages
|
|||
public readonly Color secondaryColor; |
|||
|
|||
/// Icon for message's `seen` status. For the best look use size of 16.
|
|||
public readonly Widget seenIcon; |
|||
|
|||
/// Icon for send button
|
|||
public readonly Widget sendButtonIcon; |
|||
|
|||
/// Body text style used for displaying text on different types
|
|||
/// of sent messages
|
|||
public readonly TextStyle sentMessageBodyTextStyle; |
|||
|
|||
/// Caption text style used for displaying secondary info (e.g. file size)
|
|||
/// on different types of sent messages
|
|||
public readonly TextStyle sentMessageCaptionTextStyle; |
|||
|
|||
/// Color of the document icon on sent messages. Has no effect when
|
|||
/// [documentIcon] is used.
|
|||
public readonly Color sentMessageDocumentIconColor; |
|||
|
|||
/// Text style used for displaying link description on sent messages
|
|||
public readonly TextStyle sentMessageLinkDescriptionTextStyle; |
|||
|
|||
/// Text style used for displaying link title on sent messages
|
|||
public readonly TextStyle sentMessageLinkTitleTextStyle; |
|||
|
|||
/// Colors used as backgrounds for user avatars and corresponded user names.
|
|||
/// Calculated based on a user ID, so unique across the whole app.
|
|||
public readonly List<Color> userAvatarNameColors; |
|||
|
|||
/// Text style used for displaying initials on user avatar if no
|
|||
/// image is provided
|
|||
public readonly TextStyle userAvatarTextStyle; |
|||
|
|||
/// User names text style. Color will be overwritten with [userAvatarNameColors].
|
|||
public readonly TextStyle userNameTextStyle; |
|||
|
|||
/// Creates a new chat theme based on provided colors and text styles.
|
|||
public ChatTheme( |
|||
Widget attachmentButtonIcon, |
|||
Color backgroundColor, |
|||
TextStyle dateDividerTextStyle, |
|||
Widget deliveredIcon, |
|||
Widget documentIcon, |
|||
TextStyle emptyChatPlaceholderTextStyle, |
|||
Color errorColor, |
|||
Widget errorIcon, |
|||
Color inputBackgroundColor, |
|||
BorderRadius inputBorderRadius, |
|||
TextStyle inputTextStyle, |
|||
Color inputTextColor, |
|||
float messageBorderRadius, |
|||
Color primaryColor, |
|||
TextStyle receivedMessageBodyTextStyle, |
|||
TextStyle receivedMessageCaptionTextStyle, |
|||
Color receivedMessageDocumentIconColor, |
|||
TextStyle receivedMessageLinkDescriptionTextStyle, |
|||
TextStyle receivedMessageLinkTitleTextStyle, |
|||
Color secondaryColor, |
|||
Widget seenIcon, |
|||
Widget sendButtonIcon, |
|||
TextStyle sentMessageBodyTextStyle, |
|||
TextStyle sentMessageCaptionTextStyle, |
|||
Color sentMessageDocumentIconColor, |
|||
TextStyle sentMessageLinkDescriptionTextStyle, |
|||
TextStyle sentMessageLinkTitleTextStyle, |
|||
List<Color> userAvatarNameColors, |
|||
TextStyle userAvatarTextStyle, |
|||
TextStyle userNameTextStyle |
|||
) |
|||
{ |
|||
this.attachmentButtonIcon = attachmentButtonIcon; |
|||
this.backgroundColor = backgroundColor; |
|||
this.dateDividerTextStyle = dateDividerTextStyle; |
|||
this.deliveredIcon = deliveredIcon; |
|||
this.documentIcon = documentIcon; |
|||
this.emptyChatPlaceholderTextStyle = emptyChatPlaceholderTextStyle; |
|||
this.errorColor = errorColor; |
|||
this.errorIcon = errorIcon; |
|||
this.inputBackgroundColor = inputBackgroundColor; |
|||
this.inputBorderRadius = inputBorderRadius; |
|||
this.inputTextStyle = inputTextStyle; |
|||
this.inputTextColor = inputTextColor; |
|||
this.messageBorderRadius = messageBorderRadius; |
|||
this.primaryColor = primaryColor; |
|||
this.receivedMessageBodyTextStyle = receivedMessageBodyTextStyle; |
|||
this.receivedMessageCaptionTextStyle = receivedMessageCaptionTextStyle; |
|||
this.receivedMessageDocumentIconColor = receivedMessageDocumentIconColor; |
|||
this.receivedMessageLinkDescriptionTextStyle = receivedMessageLinkDescriptionTextStyle; |
|||
this.receivedMessageLinkTitleTextStyle = receivedMessageLinkTitleTextStyle; |
|||
this.secondaryColor = secondaryColor; |
|||
this.seenIcon = seenIcon; |
|||
this.sendButtonIcon = sendButtonIcon; |
|||
this.sentMessageBodyTextStyle = sentMessageBodyTextStyle; |
|||
this.sentMessageCaptionTextStyle = sentMessageCaptionTextStyle; |
|||
this.sentMessageDocumentIconColor = sentMessageDocumentIconColor; |
|||
this.sentMessageLinkDescriptionTextStyle = sentMessageLinkDescriptionTextStyle; |
|||
this.sentMessageLinkTitleTextStyle = sentMessageLinkTitleTextStyle; |
|||
this.userAvatarNameColors = userAvatarNameColors; |
|||
this.userAvatarTextStyle = userAvatarTextStyle; |
|||
this.userNameTextStyle = userNameTextStyle; |
|||
} |
|||
} |
|||
|
|||
/// Default chat theme which extends [ChatTheme]
|
|||
internal class DefaultChatTheme : ChatTheme |
|||
{ |
|||
/// Creates a default chat theme. Use this constructor if you want to
|
|||
/// override only a couple of variables, otherwise create a new class
|
|||
/// which extends [ChatTheme]
|
|||
public DefaultChatTheme( |
|||
Widget attachmentButtonIcon =null, |
|||
Widget deliveredIcon = null, |
|||
Widget documentIcon = null, |
|||
Widget seenIcon = null, |
|||
Widget sendButtonIcon = null, |
|||
Widget errorIcon = null, |
|||
TextStyle dateDividerTextStyle = null, |
|||
TextStyle emptyChatPlaceholderTextStyle = null, |
|||
Color inputBackgroundColor = null, |
|||
BorderRadius inputBorderRadius = null, |
|||
Color inputTextColor = null, |
|||
TextStyle inputTextStyle = null, |
|||
float messageBorderRadius = 0.0f, |
|||
Color primaryColor = null, |
|||
TextStyle receivedMessageBodyTextStyle = null, |
|||
TextStyle receivedMessageCaptionTextStyle = null, |
|||
Color receivedMessageDocumentIconColor = null, |
|||
TextStyle receivedMessageLinkDescriptionTextStyle = null, |
|||
TextStyle receivedMessageLinkTitleTextStyle = null, |
|||
Color secondaryColor = null, |
|||
TextStyle sentMessageBodyTextStyle = null, |
|||
TextStyle sentMessageCaptionTextStyle = null, |
|||
Color sentMessageDocumentIconColor = null, |
|||
TextStyle sentMessageLinkDescriptionTextStyle = null, |
|||
TextStyle sentMessageLinkTitleTextStyle = null, |
|||
List<Color> userAvatarNameColors = null, |
|||
TextStyle userAvatarTextStyle = null, |
|||
TextStyle userNameTextStyle = null, |
|||
Color backgroundColor = null, |
|||
Color errorColor = null |
|||
) : base( |
|||
attachmentButtonIcon, |
|||
backgroundColor ?? ChatThemeUtils.NEUTRAL_7, |
|||
dateDividerTextStyle == null |
|||
? new TextStyle(color:ChatThemeUtils.NEUTRAL_2, fontFamily: "Avenir", fontSize:ChatThemeUtils.ratio * 12, |
|||
fontWeight: FontWeight.w800, height: 1.333f) |
|||
: dateDividerTextStyle, |
|||
deliveredIcon, |
|||
documentIcon, |
|||
emptyChatPlaceholderTextStyle == null |
|||
? new TextStyle(color:ChatThemeUtils.NEUTRAL_2, fontFamily: "Avenir", |
|||
fontSize:ChatThemeUtils.ratio * 16, fontWeight: FontWeight.w500, height: 1.5f) |
|||
: emptyChatPlaceholderTextStyle, |
|||
errorColor ?? ChatThemeUtils.ERROR, |
|||
errorIcon, |
|||
inputBackgroundColor ?? ChatThemeUtils.NEUTRAL_0, |
|||
inputBorderRadius == null ? BorderRadius.vertical(Radius.circular(20f)) : inputBorderRadius, |
|||
inputTextColor: inputTextColor ?? ChatThemeUtils.NEUTRAL_7, |
|||
inputTextStyle: inputTextStyle == null |
|||
? new TextStyle(fontFamily: "Avenir", fontSize:ChatThemeUtils.ratio * 16, fontWeight: FontWeight.w500, height: 1.5f) |
|||
: inputTextStyle, |
|||
messageBorderRadius: messageBorderRadius == 0.0 ? 20.0f : messageBorderRadius, |
|||
primaryColor: primaryColor ?? ChatThemeUtils.PRIMARY, |
|||
receivedMessageBodyTextStyle: receivedMessageBodyTextStyle == null |
|||
? new TextStyle(color:ChatThemeUtils.NEUTRAL_0, fontFamily: "Avenir", |
|||
fontSize:ChatThemeUtils.ratio * 16, fontWeight: FontWeight.w500, height: 1.5f) |
|||
: receivedMessageBodyTextStyle, |
|||
receivedMessageCaptionTextStyle: receivedMessageCaptionTextStyle == null |
|||
? new TextStyle(color:ChatThemeUtils.NEUTRAL_2, fontFamily: "Avenir", |
|||
fontSize:ChatThemeUtils.ratio * 12, fontWeight: FontWeight.w500, height: 1.333f) |
|||
: receivedMessageCaptionTextStyle, |
|||
receivedMessageDocumentIconColor: receivedMessageDocumentIconColor ?? ChatThemeUtils.PRIMARY, |
|||
receivedMessageLinkDescriptionTextStyle: |
|||
receivedMessageLinkDescriptionTextStyle == null |
|||
? new TextStyle(color:ChatThemeUtils.NEUTRAL_0, |
|||
fontFamily: "Avenir", fontSize:ChatThemeUtils.ratio * 14, fontWeight: FontWeight.w400, height: 1.428f) |
|||
: receivedMessageLinkDescriptionTextStyle, |
|||
receivedMessageLinkTitleTextStyle: receivedMessageLinkTitleTextStyle == null |
|||
? new TextStyle(color:ChatThemeUtils.NEUTRAL_0, fontFamily: "Avenir", |
|||
fontSize:ChatThemeUtils.ratio * 16, fontWeight: FontWeight.w800, height: 1.375f) |
|||
: receivedMessageLinkTitleTextStyle, |
|||
secondaryColor: secondaryColor ?? ChatThemeUtils.SECONDARY, |
|||
seenIcon: seenIcon, |
|||
sendButtonIcon: sendButtonIcon, |
|||
sentMessageBodyTextStyle: sentMessageBodyTextStyle == null |
|||
? new TextStyle(color:ChatThemeUtils.NEUTRAL_7, fontFamily: "Avenir", |
|||
fontSize:ChatThemeUtils.ratio * 16, fontWeight: FontWeight.w500, height: 1.5f) |
|||
: sentMessageBodyTextStyle, |
|||
sentMessageCaptionTextStyle: sentMessageCaptionTextStyle == null |
|||
? new TextStyle(color:ChatThemeUtils.NEUTRAL_7_WITH_OPACITY, |
|||
fontFamily: "Avenir", fontSize:ChatThemeUtils.ratio * 12, fontWeight: FontWeight.w500, height: 1.333f) |
|||
: sentMessageCaptionTextStyle, |
|||
sentMessageDocumentIconColor: sentMessageDocumentIconColor ?? ChatThemeUtils.NEUTRAL_7, |
|||
sentMessageLinkDescriptionTextStyle: |
|||
sentMessageLinkDescriptionTextStyle == null |
|||
? new TextStyle(color:ChatThemeUtils.NEUTRAL_7, fontFamily: "Avenir", |
|||
fontSize:ChatThemeUtils.ratio * 14, fontWeight: FontWeight.w400, height: 1.428f) |
|||
: sentMessageLinkDescriptionTextStyle, |
|||
sentMessageLinkTitleTextStyle: sentMessageLinkTitleTextStyle == null |
|||
? new TextStyle(color:ChatThemeUtils.NEUTRAL_7, fontFamily: "Avenir", |
|||
fontSize:ChatThemeUtils.ratio * 16, fontWeight: FontWeight.w800, height: 1.375f) |
|||
: sentMessageLinkTitleTextStyle, |
|||
userAvatarNameColors: userAvatarNameColors ?? ChatThemeUtils.COLORS, |
|||
userAvatarTextStyle: userAvatarTextStyle == null |
|||
? new TextStyle(color:ChatThemeUtils.NEUTRAL_7, fontFamily: "Avenir", fontSize:ChatThemeUtils.ratio * 12, |
|||
fontWeight: FontWeight.w800, height: 1.333f) |
|||
: userAvatarTextStyle, |
|||
userNameTextStyle: userNameTextStyle == null |
|||
? new TextStyle(fontFamily: "Avenir", fontSize:ChatThemeUtils.ratio * 12, fontWeight: FontWeight.w800, height: 1.333f) |
|||
: userNameTextStyle |
|||
) |
|||
{ |
|||
} |
|||
} |
|||
/* |
|||
/// Dark chat theme which extends [ChatTheme]
|
|||
@immutable |
|||
class DarkChatTheme extends ChatTheme { |
|||
/// Creates a dark chat theme. Use this constructor if you want to
|
|||
/// override only a couple of variables, otherwise create a new class
|
|||
/// which extends [ChatTheme]
|
|||
const DarkChatTheme({ |
|||
Widget? attachmentButtonIcon, |
|||
Color backgroundColor = DARK, |
|||
TextStyle dateDividerTextStyle = const TextStyle( |
|||
color: NEUTRAL_7, |
|||
fontFamily: 'Avenir', |
|||
fontSize: 12, |
|||
fontWeight: FontWeight.w800, |
|||
height: 1.333, |
|||
), |
|||
Widget? deliveredIcon, |
|||
Widget? documentIcon, |
|||
TextStyle emptyChatPlaceholderTextStyle = const TextStyle( |
|||
color: NEUTRAL_2, |
|||
fontFamily: 'Avenir', |
|||
fontSize: 16, |
|||
fontWeight: FontWeight.w500, |
|||
height: 1.5, |
|||
), |
|||
Color errorColor = ERROR, |
|||
Widget? errorIcon, |
|||
Color inputBackgroundColor = SECONDARY_DARK, |
|||
BorderRadius inputBorderRadius = const BorderRadius.vertical( |
|||
top: Radius.circular(20), |
|||
), |
|||
Color inputTextColor = NEUTRAL_7, |
|||
TextStyle inputTextStyle = const TextStyle( |
|||
fontFamily: 'Avenir', |
|||
fontSize: 16, |
|||
fontWeight: FontWeight.w500, |
|||
height: 1.5, |
|||
), |
|||
double messageBorderRadius = 20.0, |
|||
Color primaryColor = PRIMARY, |
|||
TextStyle receivedMessageBodyTextStyle = const TextStyle( |
|||
color: NEUTRAL_7, |
|||
fontFamily: 'Avenir', |
|||
fontSize: 16, |
|||
fontWeight: FontWeight.w500, |
|||
height: 1.5, |
|||
), |
|||
TextStyle receivedMessageCaptionTextStyle = const TextStyle( |
|||
color: NEUTRAL_7_WITH_OPACITY, |
|||
fontFamily: 'Avenir', |
|||
fontSize: 12, |
|||
fontWeight: FontWeight.w500, |
|||
height: 1.333, |
|||
), |
|||
Color receivedMessageDocumentIconColor = PRIMARY, |
|||
TextStyle receivedMessageLinkDescriptionTextStyle = const TextStyle( |
|||
color: NEUTRAL_7, |
|||
fontFamily: 'Avenir', |
|||
fontSize: 14, |
|||
fontWeight: FontWeight.w400, |
|||
height: 1.428, |
|||
), |
|||
TextStyle receivedMessageLinkTitleTextStyle = const TextStyle( |
|||
color: NEUTRAL_7, |
|||
fontFamily: 'Avenir', |
|||
fontSize: 16, |
|||
fontWeight: FontWeight.w800, |
|||
height: 1.375, |
|||
), |
|||
Color secondaryColor = SECONDARY_DARK, |
|||
Widget? seenIcon, |
|||
Widget? sendButtonIcon, |
|||
TextStyle sentMessageBodyTextStyle = const TextStyle( |
|||
color: NEUTRAL_7, |
|||
fontFamily: 'Avenir', |
|||
fontSize: 16, |
|||
fontWeight: FontWeight.w500, |
|||
height: 1.5, |
|||
), |
|||
TextStyle sentMessageCaptionTextStyle = const TextStyle( |
|||
color: NEUTRAL_7_WITH_OPACITY, |
|||
fontFamily: 'Avenir', |
|||
fontSize: 12, |
|||
fontWeight: FontWeight.w500, |
|||
height: 1.333, |
|||
), |
|||
Color sentMessageDocumentIconColor = NEUTRAL_7, |
|||
TextStyle sentMessageLinkDescriptionTextStyle = const TextStyle( |
|||
color: NEUTRAL_7, |
|||
fontFamily: 'Avenir', |
|||
fontSize: 14, |
|||
fontWeight: FontWeight.w400, |
|||
height: 1.428, |
|||
), |
|||
TextStyle sentMessageLinkTitleTextStyle = const TextStyle( |
|||
color: NEUTRAL_7, |
|||
fontFamily: 'Avenir', |
|||
fontSize: 16, |
|||
fontWeight: FontWeight.w800, |
|||
height: 1.375, |
|||
), |
|||
List<Color> userAvatarNameColors = COLORS, |
|||
TextStyle userAvatarTextStyle = const TextStyle( |
|||
color: NEUTRAL_7, |
|||
fontFamily: 'Avenir', |
|||
fontSize: 12, |
|||
fontWeight: FontWeight.w800, |
|||
height: 1.333, |
|||
), |
|||
TextStyle userNameTextStyle = const TextStyle( |
|||
fontFamily: 'Avenir', |
|||
fontSize: 12, |
|||
fontWeight: FontWeight.w800, |
|||
height: 1.333, |
|||
), |
|||
}) : super( |
|||
attachmentButtonIcon: attachmentButtonIcon, |
|||
backgroundColor: backgroundColor, |
|||
dateDividerTextStyle: dateDividerTextStyle, |
|||
deliveredIcon: deliveredIcon, |
|||
documentIcon: documentIcon, |
|||
emptyChatPlaceholderTextStyle: emptyChatPlaceholderTextStyle, |
|||
errorColor: errorColor, |
|||
errorIcon: errorIcon, |
|||
inputBackgroundColor: inputBackgroundColor, |
|||
inputBorderRadius: inputBorderRadius, |
|||
inputTextColor: inputTextColor, |
|||
inputTextStyle: inputTextStyle, |
|||
messageBorderRadius: messageBorderRadius, |
|||
primaryColor: primaryColor, |
|||
receivedMessageBodyTextStyle: receivedMessageBodyTextStyle, |
|||
receivedMessageCaptionTextStyle: receivedMessageCaptionTextStyle, |
|||
receivedMessageDocumentIconColor: receivedMessageDocumentIconColor, |
|||
receivedMessageLinkDescriptionTextStyle: |
|||
receivedMessageLinkDescriptionTextStyle, |
|||
receivedMessageLinkTitleTextStyle: receivedMessageLinkTitleTextStyle, |
|||
secondaryColor: secondaryColor, |
|||
seenIcon: seenIcon, |
|||
sendButtonIcon: sendButtonIcon, |
|||
sentMessageBodyTextStyle: sentMessageBodyTextStyle, |
|||
sentMessageCaptionTextStyle: sentMessageCaptionTextStyle, |
|||
sentMessageDocumentIconColor: sentMessageDocumentIconColor, |
|||
sentMessageLinkDescriptionTextStyle: |
|||
sentMessageLinkDescriptionTextStyle, |
|||
sentMessageLinkTitleTextStyle: sentMessageLinkTitleTextStyle, |
|||
userAvatarNameColors: userAvatarNameColors, |
|||
userAvatarTextStyle: userAvatarTextStyle, |
|||
userNameTextStyle: userNameTextStyle, |
|||
); |
|||
}*/ |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using ChatComponents; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.ui; |
|||
using UnityEngine; |
|||
using Color = Unity.UIWidgets.ui.Color; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public static class ChatUtils |
|||
{ |
|||
public static string REGEX_LINK = |
|||
@"([\w+]+\:\/\/)?([\w\d-]+\.)*[\w-]+[\.\:]\w+([\/\?\=\&\#\.]?[\w-]+)*\/?"; |
|||
|
|||
public static string formatBytes(int size, int fractionDigits = 2) |
|||
{ |
|||
List<string> profix = new List<string>() |
|||
{ |
|||
|
|||
"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" |
|||
}; |
|||
if (size <= 0) return "0 B"; |
|||
var multiple = (log(size) / log(1024)).floor(); |
|||
|
|||
return (size / Mathf.Pow(1024, multiple)).ToString("D3") + |
|||
" " + profix[multiple]; |
|||
} |
|||
|
|||
public static float log(int num) |
|||
{ |
|||
if (num == 0) |
|||
return float.NegativeInfinity; |
|||
else if (num < 0) |
|||
return Single.NaN; |
|||
else |
|||
return num * 1f; |
|||
} |
|||
|
|||
|
|||
/// Returns user avatar and name color based on the ID
|
|||
public static Color getUserAvatarNameColor(User user, List<Color> colors) |
|||
{ |
|||
var index = user.id.GetHashCode() % colors.Count; |
|||
return colors[0]; |
|||
} |
|||
|
|||
/// Returns user name as joined firstName and lastName
|
|||
public static string getUserName(User user) => |
|||
$"{user.firstName ?? ""} {user.lastName ?? ""}"; |
|||
|
|||
/// Returns formatted date used as a divider between different days in the
|
|||
/// chat history
|
|||
public static string getVerboseDateTimeRepresentation( |
|||
DateTime dateTime, |
|||
DateTime? dateFormat = null, |
|||
string dateLocale = null, |
|||
DateTime? timeFormat = null |
|||
) |
|||
{ |
|||
var formattedDate |
|||
= //dateFormat != null ? dateFormat.ToString().Format(dateTime) : DateTime.MMMd(dateLocale).format(dateTime);
|
|||
dateTime.ToString("MMM-dd"); |
|||
var formattedTime = //timeFormat != null ? timeFormat.format(dateTime) : DateTime.Hm(dateLocale).format(dateTime);
|
|||
dateTime.ToString("hh:mm"); |
|||
var localDateTime = dateTime.ToLocalTime(); |
|||
var now = DateTime.Now; |
|||
|
|||
if (localDateTime.Day == now.Day && |
|||
localDateTime.Month == now.Month && |
|||
localDateTime.Year == now.Year) |
|||
return formattedTime; |
|||
|
|||
return $"{formattedDate}, {formattedTime}"; |
|||
} |
|||
|
|||
/// Parses provided messages to chat messages (with headers and spacers) and
|
|||
/// returns them with a gallery
|
|||
public static List<object> calculateChatMessages( |
|||
List<ChatComponents.Message> messages, |
|||
User user, |
|||
bool showUserNames, |
|||
CustomDateHeaderText customDateHeaderText = null, |
|||
DateTime? dateFormat = null, |
|||
string dateLocale = null, |
|||
DateTime? timeFormat = null |
|||
) |
|||
{ |
|||
var chatMessages = new List<object>(); |
|||
var gallery = new List<PreviewImage>(); |
|||
|
|||
var shouldShowName = false; |
|||
|
|||
for (var i = messages.Count - 1; i >= 0; i--) |
|||
{ |
|||
var isFirst = i == messages.Count - 1; |
|||
var isLast = i == 0; |
|||
var message = messages[i]; |
|||
var messageHasCreatedAt = message.createdAt != null; |
|||
ChatComponents.Message nextMessage = isLast ? null : messages[i - 1]; |
|||
bool nextMessageHasCreatedAt = isLast ? true : nextMessage.createdAt != null; |
|||
bool nextMessageSameAuthor = isLast ? false : message.author.id == nextMessage.author.id; |
|||
bool notMyMessage = message.author.id != user.id; |
|||
bool nextMessageDateThreshold = false; |
|||
bool nextMessageDifferentDay = false; |
|||
bool nextMessageInGroup = false; |
|||
bool showName = false; |
|||
|
|||
if (showUserNames) |
|||
{ |
|||
var previousMessage = isFirst ? null : messages[i + 1]; |
|||
|
|||
var isFirstInGroup = notMyMessage && |
|||
(message.author.id != previousMessage?.author.id || |
|||
message.createdAt != null && |
|||
previousMessage?.createdAt != null && |
|||
(int)message.createdAt - (int)previousMessage.createdAt > 60000); |
|||
|
|||
if (isFirstInGroup) |
|||
{ |
|||
shouldShowName = false; |
|||
if (message.type == MessageType.text) |
|||
showName = true; |
|||
else |
|||
shouldShowName = true; |
|||
} |
|||
|
|||
if (message.type == MessageType.text && shouldShowName) |
|||
{ |
|||
showName = true; |
|||
shouldShowName = false; |
|||
} |
|||
} |
|||
|
|||
if (messageHasCreatedAt && nextMessageHasCreatedAt) |
|||
{ |
|||
|
|||
nextMessageDateThreshold = isLast ? false : (int) nextMessage?.createdAt - (int) message.createdAt >= 900000; |
|||
nextMessageDifferentDay = |
|||
TimeSpan.FromMilliseconds(message.createdAt == null ? 0 : (int)message.createdAt).Days != |
|||
TimeSpan.FromMilliseconds(nextMessage?.createdAt == null ? 0 : (int)nextMessage?.createdAt).Days; |
|||
|
|||
nextMessageInGroup = nextMessageSameAuthor && |
|||
(int)nextMessage?.createdAt - (int)message.createdAt <= 60000; |
|||
} |
|||
|
|||
if (isFirst && messageHasCreatedAt) |
|||
{ |
|||
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0); |
|||
var timeSend = epoch.AddTicks((message.createdAt == null ? 0 : (int) message.createdAt) * 1000000000); |
|||
chatMessages.Insert( |
|||
0, |
|||
new DateHeader( |
|||
customDateHeaderText != null |
|||
? customDateHeaderText( |
|||
new DateTime(timeSend.Year,timeSend.Month,timeSend.Day,timeSend.Hour,timeSend.Minute,timeSend.Second) |
|||
) |
|||
: getVerboseDateTimeRepresentation( |
|||
new DateTime(timeSend.Year,timeSend.Month,timeSend.Day,timeSend.Hour,timeSend.Minute,timeSend.Second),//new DateTime((int) TimeSpan.FromMilliseconds(message.createdAt == null ? 0 : (int)message.createdAt).TotalMilliseconds ),//DateTime.FromFileTime(message.createdAt == null ? 0 : (int)message.createdAt),//
|
|||
dateFormat, |
|||
dateLocale, |
|||
timeFormat |
|||
) |
|||
) |
|||
); |
|||
} |
|||
|
|||
var _message = |
|||
new Dictionary<string, object> |
|||
{ |
|||
{"message", message}, |
|||
{"nextMessageInGroup", nextMessageInGroup}, |
|||
{"showName", notMyMessage && |
|||
showUserNames && |
|||
showName && getUserName(message.author).isNotEmpty() |
|||
}, |
|||
{"showStatus", true} |
|||
}; |
|||
chatMessages.Insert(0, _message); |
|||
|
|||
if (!nextMessageInGroup) |
|||
{ |
|||
chatMessages.Insert( |
|||
0, |
|||
new MessageSpacer( |
|||
12, |
|||
message.id |
|||
) |
|||
); |
|||
} |
|||
|
|||
if (nextMessageDifferentDay || nextMessageDateThreshold) |
|||
{ |
|||
chatMessages.Insert( |
|||
0, |
|||
new DateHeader( |
|||
customDateHeaderText != null |
|||
? customDateHeaderText( |
|||
DateTime.Now)//DateTime.FromFileTime(isLast ? 0 : (int) nextMessage?.createdAt) )
|
|||
: getVerboseDateTimeRepresentation( |
|||
DateTime.Now,//DateTime.FromFileTime(isLast ? 0 : (int) nextMessage?.createdAt),
|
|||
dateFormat, |
|||
dateLocale, |
|||
timeFormat |
|||
) |
|||
) |
|||
); |
|||
} |
|||
|
|||
if (message is ImageMessage) |
|||
{ |
|||
if (foundation_.kIsWeb) |
|||
{ |
|||
if (((ImageMessage) message).uri.StartsWith("http")) |
|||
gallery.Add(new PreviewImage(message.id, ((ImageMessage) message).uri)); |
|||
} |
|||
|
|||
gallery.Add(new PreviewImage(message.id, ((ImageMessage) message).uri)); |
|||
} |
|||
} |
|||
|
|||
var results = new List<object>(){chatMessages,gallery}; |
|||
//results.AddRange(chatMessages);
|
|||
//results.AddRange(gallery);
|
|||
return results; |
|||
} |
|||
} |
|||
} |
|
|||
{ |
|||
"author": { |
|||
"firstName": "Alex", |
|||
"id": "b4878b96-efbc-479a-8291-474ef323dec7", |
|||
"imageUrl": "https://avatars.githubusercontent.com/u/14123304?v=4" |
|||
}, |
|||
"createdAt": 1598438797000, |
|||
"id": "e7a673e9-86eb-4572-936f-2882b0183cdc", |
|||
"status": "seen", |
|||
"text": "flyer chat", |
|||
"type": "text" |
|||
}& |
|||
{ |
|||
"author": { |
|||
"firstName": "Alex", |
|||
"id": "b4878b96-efbc-479a-8291-474ef323dec7", |
|||
"imageUrl": "https://avatars.githubusercontent.com/u/14123304?v=4" |
|||
}, |
|||
"createdAt": 1598438795000, |
|||
"id": "e7a673e9-86eb-4572-936f-2882b0183cda", |
|||
"status": "seen", |
|||
"text": "Dolore labore ipsum aspernatur. Omnis minima reprehenderit perspiciatis sunt rerum facilis consequatur omnis. Rerum totam est eos dolores quia qui aut. Rerum aperiam alias placeat odit non enim corporis unde molestiae. Aspernatur unde praesentium eum ut laudantium ea enim.", |
|||
"type": "text" |
|||
}& |
|||
{ |
|||
"author": { |
|||
"firstName": "Alex", |
|||
"id": "b4878b96-efbc-479a-8291-474ef323dec7", |
|||
"imageUrl": "https://avatars.githubusercontent.com/u/14123304?v=4" |
|||
}, |
|||
"createdAt": 1598438794000, |
|||
"id": "dc1a5e87-bd4e-46b0-9cb4-7d9abeda5ef8", |
|||
"status": "seen", |
|||
"text": "Ut modi corporis consequuntur dolorem omnis asperiores unde voluptatem et.", |
|||
"type": "text" |
|||
}& |
|||
{ |
|||
"author": { |
|||
"firstName": "Alex", |
|||
"id": "b4878b96-efbc-479a-8291-474ef323dec7", |
|||
"imageUrl": "https://avatars.githubusercontent.com/u/14123304?v=4" |
|||
}, |
|||
"createdAt": 1598438793000, |
|||
"id": "2a202811-7d48-4ae9-8323-d764a56031dc", |
|||
"status": "seen", |
|||
"text": "Voluptatem voluptatum eos aut voluptatem occaecati. Quia ducimus vero molestiae molestiae illum illo nisi autem. Labore consectetur expedita illum consequatur inventore consequatur quasi voluptatem. Perspiciatis ut reprehenderit officiis animi voluptas.", |
|||
"type": "text" |
|||
}& |
|||
{ |
|||
"author": { |
|||
"firstName": "Daria", |
|||
"id": "06c33e8b-e835-4736-80f4-63f44b66666c", |
|||
"imageUrl": "https://avatars.githubusercontent.com/u/33809426?v=4" |
|||
}, |
|||
"createdAt": 1598438792000, |
|||
"id": "0371b8f4-314a-448e-85ba-9c7dc5ed7a1e", |
|||
"status": "seen", |
|||
"text": "Qui ut nihil facilis quos.", |
|||
"type": "text" |
|||
}& |
|||
{ |
|||
"author": { |
|||
"firstName": "Alex", |
|||
"id": "b4878b96-efbc-479a-8291-474ef323dec7", |
|||
"imageUrl": "https://avatars.githubusercontent.com/u/14123304?v=4" |
|||
}, |
|||
"createdAt": 1598438791000, |
|||
"id": "db334c22-1fc4-4951-86b2-1eb7f7262743", |
|||
"status": "seen", |
|||
"text": "Voluptatem ducimus quo vel sunt. Vitae illum recusandae fuga eum adipisci cumque nam dolor. Aut quia et ducimus a et voluptatibus quo saepe. Vel qui ipsum assumenda quibusdam necessitatibus. Et omnis consectetur ad quod fugiat placeat omnis eligendi aut. Deleniti aspernatur corrupti beatae est et cumque architecto cumque et.", |
|||
"type": "text" |
|||
}& |
|||
{ |
|||
"author": { |
|||
"firstName": "Daria", |
|||
"id": "06c33e8b-e835-4736-80f4-63f44b66666c", |
|||
"imageUrl": "https://avatars.githubusercontent.com/u/33809426?v=4" |
|||
}, |
|||
"createdAt": 1598438790000, |
|||
"id": "f61a1924-e043-409f-9051-78ebfc68507a", |
|||
"status": "seen", |
|||
"text": "Est maxime accusantium commodi voluptatem sed. Ut nemo vero ut voluptas qui. Eaque nulla quia pariatur sed qui iure. Odit expedita voluptas quia consequuntur consequatur qui laudantium. Iusto ut voluptatem. Omnis et eos asperiores modi totam quisquam nemo.", |
|||
"type": "text" |
|||
}& |
|||
{ |
|||
"author": { |
|||
"firstName": "Daria", |
|||
"id": "06c33e8b-e835-4736-80f4-63f44b66666c", |
|||
"imageUrl": "https://avatars.githubusercontent.com/u/33809426?v=4" |
|||
}, |
|||
"createdAt": 1598438789000, |
|||
"id": "d46ffcb1-9340-4c60-8b8c-c9d17539b0c7", |
|||
"status": "seen", |
|||
"text": "Fugit sed tenetur eaque perferendis est impedit.", |
|||
"type": "text" |
|||
}& |
|||
{ |
|||
"author": { |
|||
"firstName": "Alex", |
|||
"id": "b4878b96-efbc-479a-8291-474ef323dec7", |
|||
"imageUrl": "https://avatars.githubusercontent.com/u/14123304?v=4" |
|||
}, |
|||
"createdAt": 1598438788000, |
|||
"id": "b23e5907-6d8b-4134-8cf3-c6dd34fc42d2", |
|||
"status": "seen", |
|||
"text": "Hic iure corrupti aut delectus tempore.", |
|||
"type": "text" |
|||
}& |
|||
{ |
|||
"author": { |
|||
"firstName": "Daria", |
|||
"id": "06c33e8b-e835-4736-80f4-63f44b66666c", |
|||
"imageUrl": "https://avatars.githubusercontent.com/u/33809426?v=4" |
|||
}, |
|||
"createdAt": 1598438787000, |
|||
"id": "83093c75-2efb-47bc-84d6-37dbc53bb5de", |
|||
"status": "seen", |
|||
"text": "Doloremque dolor aliquam totam dolor suscipit qui.", |
|||
"type": "text" |
|||
}& |
|||
{ |
|||
"author": { |
|||
"firstName": "Alex", |
|||
"id": "b4878b96-efbc-479a-8291-474ef323dec7", |
|||
"imageUrl": "https://avatars.githubusercontent.com/u/14123304?v=4" |
|||
}, |
|||
"createdAt": 1598438786000, |
|||
"id": "8fa70836-3309-4d09-a777-4d9603e1f123", |
|||
"status": "seen", |
|||
"text": "Architecto quaerat nulla ipsa facilis cum quasi fuga voluptate.", |
|||
"type": "text" |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text.RegularExpressions; |
|||
using ChatComponents; |
|||
using JetBrains.Annotations; |
|||
using Unity.UIWidgets.animation; |
|||
using Unity.UIWidgets.async; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using Image = Unity.UIWidgets.widgets.Image; |
|||
using TextStyle = Unity.UIWidgets.painting.TextStyle; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public delegate void OnLinkPressed(string str); |
|||
|
|||
public class LinkPreview : StatefulWidget |
|||
{ |
|||
/// Expand animation duration
|
|||
public readonly TimeSpan? animationDuration; |
|||
|
|||
/// Enables expand animation. Default value is false.
|
|||
public readonly bool? enableAnimation; |
|||
|
|||
/// Custom header above provided text
|
|||
public readonly string header; |
|||
|
|||
/// Style of the custom header
|
|||
public readonly TextStyle headerStyle; |
|||
|
|||
/// Style of highlighted links in the text
|
|||
public readonly TextStyle linkStyle; |
|||
|
|||
/// Style of preview's description
|
|||
public readonly TextStyle metadataTextStyle; |
|||
|
|||
/// Style of preview's title
|
|||
public readonly TextStyle metadataTitleStyle; |
|||
|
|||
/// Custom link press handler
|
|||
public readonly OnLinkPressed onLinkPressed; |
|||
|
|||
|
|||
/// Callback which is called when [PreviewData] was successfully parsed.
|
|||
/// Use it to save [PreviewData] to the state and pass it back
|
|||
/// to the [LinkPreview.previewData] so the [LinkPreview] would not fetch
|
|||
/// preview data again.
|
|||
public readonly OnPreviewDataFetched onPreviewDataFetched; |
|||
|
|||
/// Padding around initial text widget
|
|||
public readonly EdgeInsets padding; |
|||
|
|||
/// Pass saved [PreviewData] here so [LinkPreview] would not fetch preview
|
|||
/// data again
|
|||
public readonly PreviewData previewData; |
|||
|
|||
/// Text used for parsing
|
|||
public readonly string text; |
|||
|
|||
/// Style of the provided text
|
|||
public readonly TextStyle textStyle; |
|||
|
|||
/// Width of the [LinkPreview] widget
|
|||
public readonly float width; |
|||
|
|||
/// Creates [LinkPreview]
|
|||
public LinkPreview( |
|||
OnPreviewDataFetched onPreviewDataFetched, |
|||
PreviewData previewData, |
|||
string text, |
|||
float width, |
|||
Key key = null, |
|||
TimeSpan? animationDuration = null, |
|||
bool enableAnimation = false, |
|||
[CanBeNull] string header = null, |
|||
TextStyle headerStyle = null, |
|||
TextStyle linkStyle = null, |
|||
TextStyle metadataTextStyle = null, |
|||
TextStyle metadataTitleStyle = null, |
|||
OnLinkPressed onLinkPressed = null, |
|||
[CanBeNull] EdgeInsets padding = null, |
|||
TextStyle textStyle = null |
|||
) : base(key) |
|||
{ |
|||
this.onPreviewDataFetched = onPreviewDataFetched; |
|||
this.previewData = previewData; |
|||
this.text = text; |
|||
this.width = width; |
|||
this.animationDuration = animationDuration; |
|||
this.enableAnimation = enableAnimation; |
|||
this.header = header; |
|||
this.headerStyle = headerStyle; |
|||
this.linkStyle = linkStyle; |
|||
this.metadataTextStyle = metadataTextStyle; |
|||
this.metadataTitleStyle = metadataTitleStyle; |
|||
this.onLinkPressed = onLinkPressed; |
|||
this.padding = padding; |
|||
this.textStyle = textStyle; |
|||
} |
|||
|
|||
public override State createState() |
|||
{ |
|||
return new _LinkPreviewState(); |
|||
} |
|||
} |
|||
|
|||
public class _LinkPreviewState : SingleTickerProviderStateMixin<LinkPreview> |
|||
{ |
|||
public Animation<float> _animation; |
|||
|
|||
public AnimationController _controller; |
|||
|
|||
private bool isFetchingPreviewData; |
|||
private bool shouldAnimate; |
|||
|
|||
public override void initState() |
|||
{ |
|||
base.initState(); |
|||
didUpdateWidget(widget); |
|||
_controller = new AnimationController( |
|||
duration: widget.animationDuration ?? TimeSpan.FromMilliseconds(300), |
|||
vsync: this); |
|||
_animation = new CurvedAnimation( |
|||
parent: _controller, |
|||
curve: Curves.easeOutQuad |
|||
); |
|||
} |
|||
|
|||
public override void didUpdateWidget(StatefulWidget oldWidget) |
|||
{ |
|||
oldWidget = (LinkPreview) oldWidget; |
|||
base.didUpdateWidget(oldWidget); |
|||
|
|||
if (!isFetchingPreviewData && widget.previewData == null) _fetchData(widget.text); |
|||
|
|||
if (widget.previewData != null && ((LinkPreview) oldWidget).previewData == null) |
|||
{ |
|||
setState(() => { shouldAnimate = true; }); |
|||
_controller.reset(); |
|||
_controller.forward(); |
|||
} |
|||
else if (widget.previewData != null) |
|||
{ |
|||
setState(() => { shouldAnimate = false; }); |
|||
} |
|||
} |
|||
|
|||
public override void dispose() |
|||
{ |
|||
_controller.dispose(); |
|||
base.dispose(); |
|||
} |
|||
|
|||
private PreviewData _fetchData(string text) |
|||
{ |
|||
setState(() => { isFetchingPreviewData = true; }); |
|||
|
|||
var previewData = getPreviewData(text); |
|||
_handlePreviewDataFetched(previewData); |
|||
return previewData; |
|||
} |
|||
|
|||
private void _handlePreviewDataFetched(PreviewData previewData) |
|||
{ |
|||
Future.delayed( |
|||
widget.animationDuration ?? TimeSpan.FromMilliseconds(300) |
|||
); |
|||
|
|||
if (mounted) |
|||
{ |
|||
widget.onPreviewDataFetched(previewData); |
|||
setState(() => { isFetchingPreviewData = false; }); |
|||
} |
|||
} |
|||
|
|||
private bool _hasData(PreviewData previewData) |
|||
{ |
|||
return previewData?.title != null || |
|||
previewData?.description != null || |
|||
previewData?.image?.url != null; |
|||
} |
|||
|
|||
private bool _hasOnlyImage() |
|||
{ |
|||
return widget.previewData?.title == null && |
|||
widget.previewData?.description == null && |
|||
widget.previewData?.image?.url != null; |
|||
} |
|||
|
|||
/*private Future _onOpen(LinkableElement link) |
|||
{ |
|||
if (canLaunch(link.url)) |
|||
launch(link.url); |
|||
else |
|||
throw $"Could not launch {link}"; |
|||
}*/ |
|||
|
|||
private Widget _animated(Widget child) |
|||
{ |
|||
return new SizeTransition( |
|||
axis: Axis.vertical, |
|||
axisAlignment: -1, |
|||
sizeFactor: _animation, |
|||
child: child |
|||
); |
|||
} |
|||
|
|||
private Widget _bodyWidget(PreviewData data, string text, float width) |
|||
{ |
|||
var _padding = widget.padding ?? |
|||
EdgeInsets.only( |
|||
bottom: 16, |
|||
left: 24, |
|||
right: 24 |
|||
); |
|||
|
|||
var results = new List<Widget>(); |
|||
if (data.title != null) results.Add(_titleWidget((string)data.title )); |
|||
if (data.description != null) |
|||
results.Add(_descriptionWidget((string)data.description)); |
|||
var final_results = new List<Widget>(); |
|||
final_results.Add(new Container( |
|||
padding: EdgeInsets.only( |
|||
bottom: _padding.bottom, |
|||
left: _padding.left, |
|||
right: _padding.right |
|||
), |
|||
child: new Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: results |
|||
) |
|||
)); |
|||
if (data.image?.url != null) final_results.Add(_imageWidget(data.image.url, width)); |
|||
return new Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: final_results |
|||
); |
|||
} |
|||
|
|||
private Widget _containerWidget( |
|||
bool animate, |
|||
bool withPadding = false, |
|||
Widget child = null |
|||
) |
|||
{ |
|||
var _padding = widget.padding ?? |
|||
EdgeInsets.symmetric( |
|||
horizontal: 24, |
|||
vertical: 16 |
|||
); |
|||
|
|||
var shouldAnimate = widget.enableAnimation == true && animate; |
|||
var results = new List<Widget>(); |
|||
if (widget.header != null) |
|||
results.Add(new Padding( |
|||
padding: EdgeInsets.only(bottom: 6), |
|||
child: new Text( |
|||
(string)widget.header, |
|||
maxLines: 1, |
|||
overflow: TextOverflow.ellipsis, |
|||
style: widget.headerStyle |
|||
) |
|||
)); |
|||
//results.Add(_linkify());
|
|||
if (withPadding && child != null) |
|||
results.Add(shouldAnimate ? _animated(child) : child); |
|||
var final_results = new List<Widget>(); |
|||
final_results.Add(new Padding( |
|||
padding: withPadding |
|||
? EdgeInsets.all(0) |
|||
: EdgeInsets.only( |
|||
_padding.left, |
|||
right: _padding.right, |
|||
top: _padding.top, |
|||
bottom: _hasOnlyImage() ? 0 : 16 |
|||
), |
|||
child: new Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: results |
|||
) |
|||
)); |
|||
if (!withPadding && child != null) |
|||
final_results.Add(shouldAnimate ? _animated(child) : child); |
|||
return new Container( |
|||
constraints: new BoxConstraints(maxWidth: widget.width), |
|||
padding: withPadding ? _padding : null, |
|||
child: new Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: final_results |
|||
) |
|||
); |
|||
} |
|||
|
|||
private Widget _descriptionWidget(string description) |
|||
{ |
|||
return new Container( |
|||
margin: EdgeInsets.only(top: 8), |
|||
child: new Text( |
|||
description, |
|||
maxLines: 3, |
|||
overflow: TextOverflow.ellipsis, |
|||
style: widget.metadataTextStyle |
|||
) |
|||
); |
|||
} |
|||
|
|||
private Widget _imageWidget(string url, float width) |
|||
{ |
|||
return new Container( |
|||
constraints: new BoxConstraints( |
|||
maxHeight: width |
|||
), |
|||
width: width, |
|||
child: Image.network( |
|||
url, |
|||
fit: BoxFit.fitWidth |
|||
) |
|||
); |
|||
} |
|||
|
|||
/*private Widget _linkify() |
|||
{ |
|||
return SelectableLinkify( |
|||
linkifiers:[UrlLinkifier()], |
|||
widget.linkStyle, |
|||
100, |
|||
1, |
|||
widget.onLinkPressed != null |
|||
? element => widget.onLinkPressed!(element.url) |
|||
: _onOpen, |
|||
LinkifyOptions( |
|||
defaultToHttps: true, |
|||
humanize: false, |
|||
looseUrl: true, |
|||
), |
|||
widget.text, |
|||
widget.textStyle |
|||
); |
|||
}*/ |
|||
|
|||
private Widget _minimizedBodyWidget(PreviewData data, string text) |
|||
{ |
|||
var inner_results = new List<Widget>(); |
|||
if (data.title != null) |
|||
inner_results.Add(_titleWidget(data.title)); |
|||
if (data.description != null) |
|||
inner_results.Add(_descriptionWidget(data.description)); |
|||
|
|||
if (data.image.url != null) |
|||
_minimizedImageWidget(data.image.url); |
|||
var results = new List<Widget>(); |
|||
results.Add(new Expanded( |
|||
child: new Container( |
|||
margin: EdgeInsets.only(right: 4), |
|||
child: new Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: inner_results |
|||
) |
|||
) |
|||
)); |
|||
if (data.image.url != null) |
|||
results.Add(_minimizedImageWidget(data.image.url)); |
|||
var outerResults = (Widget) null; |
|||
if (data.title != null || data.description != null) |
|||
outerResults = new Container( |
|||
margin: EdgeInsets.only(top: 16), |
|||
child: new Row( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: results |
|||
) |
|||
); |
|||
return new Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: new List<Widget> {outerResults} |
|||
); |
|||
} |
|||
|
|||
private Widget _minimizedImageWidget(string url) |
|||
{ |
|||
return new ClipRRect( |
|||
borderRadius: BorderRadius.all( |
|||
Radius.circular(12) |
|||
), |
|||
child: new SizedBox( |
|||
height: 48, |
|||
width: 48, |
|||
child: Image.network(url) |
|||
) |
|||
); |
|||
} |
|||
|
|||
private Widget _titleWidget(string title) |
|||
{ |
|||
var style = widget.metadataTitleStyle ?? |
|||
new TextStyle( |
|||
fontWeight: FontWeight.bold |
|||
); |
|||
|
|||
return new Text( |
|||
title, |
|||
maxLines: 2, |
|||
overflow: TextOverflow.ellipsis, |
|||
style: style |
|||
); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
if (widget.previewData != null && _hasData(widget.previewData)) |
|||
{ |
|||
var aspectRatio = widget.previewData.image == null |
|||
? 0f |
|||
: widget.previewData.image.width / |
|||
widget.previewData.image.height; |
|||
|
|||
var _width = aspectRatio == 1 ? widget.width : widget.width - 32; |
|||
|
|||
return _containerWidget( |
|||
shouldAnimate, |
|||
child: aspectRatio == 1 |
|||
? _minimizedBodyWidget(widget.previewData, widget.text) |
|||
: _bodyWidget(widget.previewData, widget.text, _width), |
|||
withPadding: aspectRatio == 1 |
|||
); |
|||
} |
|||
|
|||
return _containerWidget(false); |
|||
} |
|||
|
|||
private PreviewData getPreviewData(string text) |
|||
{ |
|||
var previewData = new PreviewData(); |
|||
|
|||
string previewDataDescription = null; |
|||
PreviewDataImage previewDataImage = null; |
|||
|
|||
string previewDataTitle =""; |
|||
string previewDataUrl =""; |
|||
var REGEX_LINK = |
|||
@"([\w+]+\:\/\/)?([\w\d-]+\.)*[\w-]+[\.\:]\w+([\/\?\=\&\#\.]?[\w-]+)*\/?"; |
|||
var REGEX_IMAGE_CONTENT_TYPE = @"image\/*"; |
|||
/* try |
|||
{ |
|||
var urlRegexp = new Regex(REGEX_LINK); |
|||
var matches = urlRegexp.Match(text.ToLower()); |
|||
if (matches.Length == 0) return previewData; |
|||
|
|||
var url = text.Substring(matches.first.start, matches.first.end); |
|||
if (!url.ToLower().StartsWith("http")) url = "https://" + url; |
|||
previewDataUrl = url; |
|||
var uri = Uri.parse(url); |
|||
var response = http.get(uri); |
|||
var document = parser.parse(response.body); |
|||
|
|||
var imageRegexp = new Regex(REGEX_IMAGE_CONTENT_TYPE); |
|||
|
|||
if (imageRegexp.hasMatch(response.headers["content-type"] ?? "")) |
|||
{ |
|||
var imageSize = _getImageSize(previewDataUrl); |
|||
previewDataImage = new PreviewDataImage( |
|||
imageSize.height, |
|||
previewDataUrl, |
|||
imageSize.width |
|||
); |
|||
return new PreviewData( |
|||
image: previewDataImage, |
|||
link: previewDataUrl |
|||
); |
|||
} |
|||
|
|||
if (!_hasUTF8Charset(document)) return previewData; |
|||
|
|||
var title = _getTitle(document); |
|||
if (title != null) previewDataTitle = title.trim(); |
|||
|
|||
var description = _getDescription(document); |
|||
if (description != null) previewDataDescription = description.trim(); |
|||
|
|||
var imageUrls = _getImageUrls(document, url); |
|||
|
|||
Size imageSize; |
|||
string imageUrl; |
|||
|
|||
if (imageUrls.isNotEmpty) |
|||
{ |
|||
imageUrl = imageUrls.length == 1 |
|||
? imageUrls[0] |
|||
: _getBiggestImageUrl(imageUrls); |
|||
|
|||
imageSize = _getImageSize(imageUrl); |
|||
previewDataImage = new PreviewDataImage( |
|||
imageSize.height, |
|||
imageUrl, |
|||
imageSize.width |
|||
); |
|||
} |
|||
|
|||
return new PreviewData( |
|||
previewDataDescription, |
|||
previewDataImage, |
|||
previewDataUrl, |
|||
previewDataTitle |
|||
); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return new PreviewData( |
|||
previewDataDescription, |
|||
previewDataImage, |
|||
previewDataUrl, |
|||
previewDataTitle |
|||
); |
|||
}*/ |
|||
return new PreviewData( |
|||
previewDataDescription, |
|||
previewDataImage, |
|||
previewDataUrl, |
|||
previewDataTitle |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text.RegularExpressions; |
|||
using ChatComponents; |
|||
using JetBrains.Annotations; |
|||
using Unity.UIWidgets.animation; |
|||
using Unity.UIWidgets.async; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using UnityEngine.UIElements; |
|||
using Image = Unity.UIWidgets.widgets.Image; |
|||
using TextStyle = Unity.UIWidgets.painting.TextStyle; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public static class urlLinkifierUtils |
|||
{ |
|||
public static Regex _urlRegex = new Regex( |
|||
@"^(.*?)((?:https?:\/\/|www\.)[^\s/$.?#].[^\s]*)" |
|||
|
|||
); |
|||
|
|||
public static Regex _looseUrlRegex =new Regex( |
|||
@"^(.*?)((https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*))" |
|||
|
|||
); |
|||
|
|||
public static Regex _protocolIdentifierRegex =new Regex( |
|||
@"^(https?:\/\/)" |
|||
|
|||
); |
|||
|
|||
} |
|||
// Utility class that implements [Linkifier.parse] method.
|
|||
/// Used to find links in the text.
|
|||
/*public class UrlLinkifier : Linkifier { |
|||
/// Default constructor
|
|||
UrlLinkifier(); |
|||
|
|||
/// Parses text to find all links inside it
|
|||
@override |
|||
List<LinkifyElement> parse(elements, options) { |
|||
final list = <LinkifyElement>[]; |
|||
|
|||
elements.forEach((element) { |
|||
if (element is TextElement) { |
|||
var loose = false; |
|||
var match = _urlRegex.firstMatch(element.text); |
|||
|
|||
if (match?.group(1)?.isNotEmpty == true) { |
|||
final looseMatch = _looseUrlRegex.firstMatch(match!.group(1)!); |
|||
if (looseMatch != null) { |
|||
match = looseMatch; |
|||
loose = true; |
|||
} |
|||
} |
|||
|
|||
if (match == null && options.looseUrl) { |
|||
match = _looseUrlRegex.firstMatch(element.text); |
|||
loose = true; |
|||
} |
|||
|
|||
if (match == null) { |
|||
list.add(element); |
|||
} else { |
|||
final text = element.text.replaceFirst(match.group(0)!, ""); |
|||
|
|||
if (match.group(1)?.isNotEmpty == true) { |
|||
list.add(TextElement(match.group(1)!)); |
|||
} |
|||
|
|||
if (match.group(2)?.isNotEmpty == true) { |
|||
var originalUrl = match.group(2)!; |
|||
String? end; |
|||
|
|||
if (options.excludeLastPeriod && |
|||
originalUrl[originalUrl.length - 1] == ".") { |
|||
end = "."; |
|||
originalUrl = originalUrl.substring(0, originalUrl.length - 1); |
|||
} |
|||
|
|||
final url = originalUrl; |
|||
|
|||
if (loose || !originalUrl.startsWith(_protocolIdentifierRegex)) { |
|||
originalUrl = (options.defaultToHttps ? "https://" : "http://") + |
|||
originalUrl; |
|||
} |
|||
|
|||
list.add( |
|||
UrlElement( |
|||
originalUrl, |
|||
url, |
|||
), |
|||
); |
|||
|
|||
if (end != null) { |
|||
list.add(TextElement(end)); |
|||
} |
|||
} |
|||
|
|||
if (text.isNotEmpty) { |
|||
list.addAll(parse([TextElement(text)], options)); |
|||
} |
|||
} |
|||
} else { |
|||
list.add(element); |
|||
} |
|||
}); |
|||
|
|||
return list; |
|||
} |
|||
} |
|||
|
|||
/// Represents an element containing a link
|
|||
|
|||
class UrlElement : LinkableElement { |
|||
/// Creates [UrlElement]
|
|||
UrlElement(String url, [String? text]) : super(text, url); |
|||
|
|||
@override |
|||
String toString() { |
|||
return "LinkElement: "$url" ($text)"; |
|||
} |
|||
|
|||
@override |
|||
bool operator ==(other) => equals(other); |
|||
|
|||
@override |
|||
bool equals(other) => other is UrlElement && super.equals(other); |
|||
|
|||
@override |
|||
// ignore: unnecessary_overrides
|
|||
int get hashCode => super.hashCode; |
|||
}*/ |
|||
|
|||
//}
|
|||
} |
|
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace Unity.UIWidgets.material |
|||
{ |
|||
public static class CircleAvatarUtils |
|||
{ |
|||
// The default radius if nothing is specified.
|
|||
public static float _defaultRadius = 20.0f; |
|||
|
|||
// The default min if only the max is specified.
|
|||
public static float _defaultMinRadius = 0.0f; |
|||
|
|||
// The default max if only the min is specified.
|
|||
public static float _defaultMaxRadius = float.PositiveInfinity; |
|||
} |
|||
|
|||
public class CircleAvatar : StatelessWidget |
|||
{ |
|||
/// The color with which to fill the circle. Changing the background
|
|||
/// color will cause the avatar to animate to the new color.
|
|||
///
|
|||
/// If a [backgroundColor] is not specified, the theme's
|
|||
/// [ThemeData.primaryColorLight] is used with dark foreground colors, and
|
|||
/// [ThemeData.primaryColorDark] with light foreground colors.
|
|||
public readonly Color backgroundColor; |
|||
|
|||
/// The background image of the circle. Changing the background
|
|||
/// image will cause the avatar to animate to the new image.
|
|||
///
|
|||
/// Typically used as a fallback image for [foregroundImage].
|
|||
///
|
|||
/// If the [CircleAvatar] is to have the user's initials, use [child] instead.
|
|||
public readonly ImageProvider backgroundImage; |
|||
|
|||
/// The widget below this widget in the tree.
|
|||
///
|
|||
/// Typically a [Text] widget. If the [CircleAvatar] is to have an image, use
|
|||
/// [backgroundImage] instead.
|
|||
public readonly Widget child; |
|||
|
|||
/// The default text color for text in the circle.
|
|||
///
|
|||
/// Defaults to the primary text theme color if no [backgroundColor] is
|
|||
/// specified.
|
|||
///
|
|||
/// Defaults to [ThemeData.primaryColorLight] for dark background colors, and
|
|||
/// [ThemeData.primaryColorDark] for light background colors.
|
|||
public readonly Color foregroundColor; |
|||
|
|||
/// The foreground image of the circle.
|
|||
///
|
|||
/// Typically used as profile image. For fallback use [backgroundImage].
|
|||
public readonly ImageProvider foregroundImage; |
|||
|
|||
/// The maximum size of the avatar, expressed as the radius (half the
|
|||
/// diameter).
|
|||
///
|
|||
/// If [maxRadius] is specified, then [radius] must not also be specified.
|
|||
///
|
|||
/// Defaults to [double.infinity].
|
|||
///
|
|||
/// Constraint changes are animated, but size changes due to the environment
|
|||
/// itself changing are not. For example, changing the [maxRadius] from 10 to
|
|||
/// 20 when the [CircleAvatar] is in an unconstrained environment will cause
|
|||
/// the avatar to animate from a 20 pixel diameter to a 40 pixel diameter.
|
|||
/// However, if the [maxRadius] is 40 and the [CircleAvatar] has a parent
|
|||
/// [SizedBox] whose size changes instantaneously from 20 pixels to 40 pixels,
|
|||
/// the size will snap to 40 pixels instantly.
|
|||
public readonly float? maxRadius; |
|||
|
|||
/// The minimum size of the avatar, expressed as the radius (half the
|
|||
/// diameter).
|
|||
///
|
|||
/// If [minRadius] is specified, then [radius] must not also be specified.
|
|||
///
|
|||
/// Defaults to zero.
|
|||
///
|
|||
/// Constraint changes are animated, but size changes due to the environment
|
|||
/// itself changing are not. For example, changing the [minRadius] from 10 to
|
|||
/// 20 when the [CircleAvatar] is in an unconstrained environment will cause
|
|||
/// the avatar to animate from a 20 pixel diameter to a 40 pixel diameter.
|
|||
/// However, if the [minRadius] is 40 and the [CircleAvatar] has a parent
|
|||
/// [SizedBox] whose size changes instantaneously from 20 pixels to 40 pixels,
|
|||
/// the size will snap to 40 pixels instantly.
|
|||
public readonly float? minRadius; |
|||
|
|||
/// An optional error callback for errors emitted when loading
|
|||
/// [backgroundImage].
|
|||
public readonly ImageErrorListener onBackgroundImageError; |
|||
|
|||
/// An optional error callback for errors emitted when loading
|
|||
/// [foregroundImage].
|
|||
public readonly ImageErrorListener onForegroundImageError; |
|||
|
|||
/// The size of the avatar, expressed as the radius (half the diameter).
|
|||
///
|
|||
/// If [radius] is specified, then neither [minRadius] nor [maxRadius] may be
|
|||
/// specified. Specifying [radius] is equivalent to specifying a [minRadius]
|
|||
/// and [maxRadius], both with the value of [radius].
|
|||
///
|
|||
/// If neither [minRadius] nor [maxRadius] are specified, defaults to 20
|
|||
/// logical pixels. This is the appropriate size for use with
|
|||
/// [ListTile.leading].
|
|||
///
|
|||
/// Changes to the [radius] are animated (including changing from an explicit
|
|||
/// [radius] to a [minRadius]/[maxRadius] pair or vice versa).
|
|||
public readonly float? radius; |
|||
|
|||
/// Creates a circle that represents a user.
|
|||
public CircleAvatar( |
|||
Key key = null, |
|||
Widget child = null, |
|||
Color backgroundColor = null, |
|||
Color foregroundColor = null, |
|||
ImageProvider backgroundImage = null, |
|||
ImageProvider foregroundImage = null, |
|||
ImageErrorListener onBackgroundImageError = null, |
|||
ImageErrorListener onForegroundImageError = null, |
|||
float radius = 0.0f, |
|||
float minRadius = 0.0f, |
|||
float maxRadius = 0.0f |
|||
) : base(key) |
|||
{ |
|||
D.assert(radius == null || minRadius == null && maxRadius == null); |
|||
D.assert(backgroundImage != null || onBackgroundImageError == null); |
|||
D.assert(foregroundImage != null || onForegroundImageError == null); |
|||
this.child = child; |
|||
this.backgroundColor = backgroundColor; |
|||
this.backgroundImage = backgroundImage; |
|||
this.foregroundImage = foregroundImage; |
|||
this.onBackgroundImageError = onBackgroundImageError; |
|||
this.onForegroundImageError = onForegroundImageError; |
|||
this.foregroundColor = foregroundColor; |
|||
this.radius = radius; |
|||
this.minRadius = minRadius; |
|||
this.maxRadius = maxRadius; |
|||
} |
|||
|
|||
|
|||
private float _minDiameter |
|||
{ |
|||
get |
|||
{ |
|||
if (radius == null && minRadius == null && maxRadius == null) |
|||
return CircleAvatarUtils._defaultRadius * 2.0f; |
|||
return 2.0f * (radius ?? minRadius ?? CircleAvatarUtils._defaultMinRadius); |
|||
} |
|||
} |
|||
|
|||
private float _maxDiameter |
|||
{ |
|||
get |
|||
{ |
|||
if (radius == null && minRadius == null && maxRadius == null) |
|||
return CircleAvatarUtils._defaultRadius * 2.0f; |
|||
return 2.0f * (radius ?? maxRadius ?? CircleAvatarUtils._defaultMaxRadius); |
|||
} |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
D.assert(WidgetsD.debugCheckHasMediaQuery(context)); |
|||
var theme = Theme.of(context); |
|||
var textStyle = theme.primaryTextTheme.subtitle1.copyWith(color: foregroundColor); |
|||
var effectiveBackgroundColor = backgroundColor; |
|||
if (effectiveBackgroundColor == null) |
|||
switch (ThemeData.estimateBrightnessForColor(textStyle.color)) |
|||
{ |
|||
case Brightness.dark: |
|||
effectiveBackgroundColor = theme.primaryColorLight; |
|||
break; |
|||
case Brightness.light: |
|||
effectiveBackgroundColor = theme.primaryColorDark; |
|||
break; |
|||
} |
|||
else if (foregroundColor == null) |
|||
switch (ThemeData.estimateBrightnessForColor(backgroundColor)) |
|||
{ |
|||
case Brightness.dark: |
|||
textStyle = textStyle.copyWith(color: theme.primaryColorLight); |
|||
break; |
|||
case Brightness.light: |
|||
textStyle = textStyle.copyWith(color: theme.primaryColorDark); |
|||
break; |
|||
} |
|||
|
|||
var minDiameter = _minDiameter; |
|||
var maxDiameter = _maxDiameter; |
|||
return new AnimatedContainer( |
|||
constraints: new BoxConstraints( |
|||
minHeight: minDiameter, |
|||
minWidth: minDiameter, |
|||
maxWidth: maxDiameter, |
|||
maxHeight: maxDiameter |
|||
), |
|||
duration: material_.kThemeChangeDuration, |
|||
decoration: new BoxDecoration( |
|||
effectiveBackgroundColor, |
|||
backgroundImage != null |
|||
? new DecorationImage( |
|||
backgroundImage, |
|||
onBackgroundImageError, |
|||
fit: BoxFit.cover |
|||
) |
|||
: null, |
|||
shape: BoxShape.circle |
|||
), |
|||
foregroundDecoration: foregroundImage != null |
|||
? new BoxDecoration( |
|||
image: new DecorationImage( |
|||
foregroundImage, |
|||
onForegroundImageError, |
|||
fit: BoxFit.cover |
|||
), |
|||
shape: BoxShape.circle |
|||
) |
|||
: null, |
|||
child: child == null |
|||
? null |
|||
: new Center( |
|||
child: new MediaQuery( |
|||
// Need to ignore the ambient textScaleFactor here so that the
|
|||
// text doesn't escape the avatar when the textScaleFactor is large.
|
|||
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0f), |
|||
child: new IconTheme( |
|||
data: theme.iconTheme.copyWith(textStyle.color), |
|||
child: new DefaultTextStyle( |
|||
style: textStyle, |
|||
child: child |
|||
) |
|||
) |
|||
) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using JetBrains.Annotations; |
|||
using UIWidgetsSample; |
|||
using Unity.UIWidgets.foundation; |
|||
|
|||
namespace ChatComponents |
|||
{ |
|||
public enum MessageType |
|||
{ |
|||
custom, |
|||
file, |
|||
image, |
|||
text, |
|||
unsupported |
|||
} |
|||
|
|||
/// Extension with one [toShortString] method
|
|||
public class MessageTypeToShortString |
|||
{ |
|||
/// Converts enum to the string equal to enum's name
|
|||
public MessageType messageType; |
|||
|
|||
private string toShortString() |
|||
{ |
|||
return ToString().Split('.').last(); |
|||
} |
|||
} |
|||
|
|||
/// All possible statuses message can have.
|
|||
public enum Status |
|||
{ |
|||
delivered, |
|||
error, |
|||
seen, |
|||
sending, |
|||
sent |
|||
} |
|||
|
|||
/// Extension with one [toShortString] method
|
|||
public class StatusToShortString |
|||
{ |
|||
public Status status; |
|||
|
|||
/// Converts enum to the string equal to enum's name
|
|||
private string toShortString() |
|||
{ |
|||
return ToString().Split('.').last(); |
|||
} |
|||
} |
|||
|
|||
/// An abstract class that contains all variables and methods
|
|||
/// every message will have.
|
|||
public abstract class Message : Equatable |
|||
{ |
|||
/// User who sent this message
|
|||
public readonly User author; |
|||
|
|||
/// Created message timestamp, in ms
|
|||
public readonly int? createdAt; |
|||
|
|||
/// Unique ID of the message
|
|||
public readonly string id; |
|||
|
|||
/// Additional custom metadata or attributes related to the message
|
|||
public readonly Dictionary<string, object> metadata; |
|||
|
|||
/// ID of the room where this message is sent
|
|||
public readonly string roomId; |
|||
|
|||
/// Message [Status]
|
|||
public readonly Status? status; |
|||
|
|||
/// [MessageType]
|
|||
public readonly MessageType type; |
|||
|
|||
public Message( |
|||
User author, |
|||
int? createdAt, |
|||
string id, |
|||
Dictionary<string, object> metadata, |
|||
[CanBeNull] string roomId, |
|||
Status? status, |
|||
MessageType type |
|||
) |
|||
{ |
|||
this.author = author; |
|||
this.createdAt = createdAt; |
|||
this.id = id; |
|||
this.metadata = metadata; |
|||
this.roomId = roomId; |
|||
this.status = status; |
|||
this.type = type; |
|||
} |
|||
|
|||
/// Creates a particular message from a map (decoded JSON).
|
|||
/// Type is determined by the `type` field.
|
|||
public static Message fromJson(Dictionary<string, object> json) |
|||
{ |
|||
var type = json["type"] as string; |
|||
|
|||
switch (type) |
|||
{ |
|||
case "custom": |
|||
return CustomMessage.fromJson(json); |
|||
case "file": |
|||
return FileMessage.fromJson(json); |
|||
case "image": |
|||
return ImageMessage.fromJson(json); |
|||
case "text": |
|||
return TextMessage.fromJson(json); |
|||
default: |
|||
return UnsupportedMessage.fromJson(json); |
|||
} |
|||
} |
|||
|
|||
/// Creates a copy of the message with an updated data
|
|||
/// [metadata] with null value will nullify existing metadata, otherwise
|
|||
/// both metadatas will be merged into one Map, where keys from a passed
|
|||
/// metadata will overwite keys from the previous one.
|
|||
/// [previewData] will be only set for the text message type.
|
|||
/// [status] with null value will be overwritten by the previous status.
|
|||
/// [text] will be only set for the text message type. Null value will be
|
|||
/// overwritten by the previous text (can't be empty).
|
|||
public abstract Message copyWith( |
|||
Dictionary<string, object> metadata = null, |
|||
[CanBeNull] PreviewData previewData = null, |
|||
Status? status = default, |
|||
string text = null |
|||
); |
|||
|
|||
/// Converts a particular message to the map representation, encodable to JSON.\
|
|||
public abstract Dictionary<string, object> toJson(); |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using UIWidgetsSample; |
|||
|
|||
namespace ChatComponents |
|||
{ |
|||
public enum RoomType |
|||
{ |
|||
channel, |
|||
direct, |
|||
group, |
|||
unsupported |
|||
} |
|||
|
|||
|
|||
/// A class that represents a room where 2 or more participants can chat
|
|||
public class Room : Equatable |
|||
{ |
|||
/// Created room timestamp, in ms
|
|||
public readonly int? createdAt; |
|||
|
|||
/// Room"s unique ID
|
|||
public readonly string id; |
|||
|
|||
/// Room"s image. In case of the [RoomType.direct] - avatar of the second person,
|
|||
/// otherwise a custom image [RoomType.group].
|
|||
public readonly string imageUrl; |
|||
|
|||
/// Additional custom metadata or attributes related to the room
|
|||
public readonly Dictionary<string, object> metadata; |
|||
|
|||
/// Room"s name. In case of the [RoomType.direct] - name of the second person,
|
|||
/// otherwise a custom name [RoomType.group].
|
|||
public readonly string name; |
|||
|
|||
/// [RoomType]
|
|||
public readonly RoomType type; |
|||
|
|||
/// List of users which are in the room
|
|||
public readonly List<User> users; |
|||
|
|||
/// Creates a [Room]
|
|||
public Room( |
|||
RoomType type, |
|||
List<User> users, |
|||
string id, |
|||
int? createdAt = null, |
|||
string imageUrl = null, |
|||
Dictionary<string, object> metadata = null, |
|||
string name = null |
|||
) |
|||
{ |
|||
this.type = type; |
|||
this.users = users; |
|||
this.id = id; |
|||
this.createdAt = createdAt; |
|||
this.imageUrl = imageUrl; |
|||
this.metadata = metadata; |
|||
this.name = name; |
|||
} |
|||
|
|||
/// Equatable props
|
|||
public override List<object> props => new List<object> |
|||
{createdAt, id, imageUrl, metadata, name, type, users}; |
|||
|
|||
/// Creates room from a map (decoded JSON).
|
|||
public Room fromJson(Dictionary<string, object> json) |
|||
{ |
|||
var users = (json["users"] as List<Dictionary<string, object>>); |
|||
var usersList = new List<User>(); |
|||
foreach (var user in users){ |
|||
if (User.fromJson(user) != null) |
|||
{ |
|||
usersList.Add(User.fromJson(user)); |
|||
} |
|||
|
|||
} |
|||
|
|||
return new Room( |
|||
createdAt: json["createdAt"] as int?, |
|||
id: json["id"] as string, |
|||
imageUrl: json["imageUrl"] as string, |
|||
metadata: json["metadata"] as Dictionary<string, object>, |
|||
name: json["name"] as string, |
|||
type: ChatRoomUtils.getRoomTypeFromString(json["type"] as string), |
|||
users: usersList); |
|||
} |
|||
|
|||
/// Converts room to the map representation, encodable to JSON.
|
|||
public Dictionary<string, object> toJson() |
|||
{ |
|||
var users = new List<User>(); |
|||
return new Dictionary<string, object> |
|||
{ |
|||
{"createdAt", createdAt}, |
|||
{"id", id}, |
|||
{"imageUrl", imageUrl}, |
|||
{"metadata", metadata}, |
|||
{"name", name}, |
|||
{"type", ChatRoomUtils.toShortString(type)}, |
|||
{"users", users.Where(e => e.toJson() != null).ToList()} |
|||
}; |
|||
} |
|||
|
|||
/// Creates a copy of the room with an updated data.
|
|||
/// [imageUrl] and [name] with null values will nullify existing values
|
|||
/// [metadata] with null value will nullify existing metadata, otherwise
|
|||
/// both metadatas will be merged into one Map, where keys from a passed
|
|||
/// metadata will overwite keys from the previous one.
|
|||
/// [type] and [users] with null values will be overwritten by previous values.
|
|||
private Room copyWith( |
|||
string imageUrl = null, |
|||
Dictionary<string, object> metadata = null, |
|||
string name = null, |
|||
RoomType? type = null, |
|||
List<User> users = null |
|||
) |
|||
{ |
|||
var result = new Dictionary<string, object>(); |
|||
if (this.metadata != null) |
|||
foreach (var metaItem in this.metadata) |
|||
result.Add(metaItem.Key, metaItem.Value); |
|||
foreach (var metaItem in metadata) result.Add(metaItem.Key, metaItem.Value); |
|||
|
|||
|
|||
return new Room( |
|||
id: id, |
|||
imageUrl: imageUrl, |
|||
metadata: metadata == null |
|||
? null |
|||
: result, |
|||
name: name, |
|||
type: type ?? this.type, |
|||
users: users ?? this.users |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
using UIWidgetsSample; |
|||
using Unity.UIWidgets.foundation; |
|||
|
|||
namespace ChatComponents |
|||
{ |
|||
public enum Role |
|||
{ |
|||
admin, |
|||
agent, |
|||
moderator, |
|||
user |
|||
} |
|||
|
|||
/// Extension with one [toShortString] method
|
|||
/// RoleToShortString
|
|||
public class RoleToShortString |
|||
{ |
|||
/// Converts enum to the string equal to enum's name
|
|||
public Role role; |
|||
|
|||
public string toShortString() |
|||
{ |
|||
return ToString().Split('.').last(); |
|||
} |
|||
} |
|||
|
|||
/// A class that represents user.
|
|||
public class User : Equatable |
|||
{ |
|||
/// Created user timestamp, in ms
|
|||
public readonly int? createdAt; |
|||
|
|||
/// First name of the user
|
|||
public readonly string firstName; |
|||
|
|||
/// Unique ID of the user
|
|||
public readonly string id; |
|||
|
|||
/// Remote image URL representing user's avatar
|
|||
public readonly string imageUrl; |
|||
|
|||
/// Last name of the user
|
|||
public readonly string lastName; |
|||
|
|||
/// Timestamp when user was last visible, in ms
|
|||
public readonly int? lastSeen; |
|||
|
|||
/// Additional custom metadata or attributes related to the user
|
|||
public readonly Dictionary<string, object> metadata; |
|||
|
|||
/// User [Role]
|
|||
public Role? role; |
|||
|
|||
/// Creates a user.
|
|||
public User( |
|||
string id, |
|||
int? createdAt = null, |
|||
string firstName = null, |
|||
string imageUrl = null, |
|||
string lastName = null, |
|||
int? lastSeen = null, |
|||
Dictionary<string, object> metadata = null, |
|||
Role? role = null |
|||
) |
|||
{ |
|||
this.createdAt = createdAt; |
|||
this.firstName = firstName; |
|||
this.id = id; |
|||
this.imageUrl = imageUrl; |
|||
this.lastName = lastName; |
|||
this.lastSeen = lastSeen; |
|||
this.metadata = metadata; |
|||
this.role = role; |
|||
} |
|||
|
|||
/// Equatable props
|
|||
public override List<object> props => new List<object> |
|||
{createdAt, firstName, id, imageUrl, lastName, lastSeen, metadata, role}; |
|||
|
|||
/// Creates user from a map (decoded JSON).
|
|||
public static User fromJson(Dictionary<string, object> json) |
|||
{ |
|||
return new User( |
|||
createdAt: json["createdAt"] as int?, |
|||
firstName: json["firstName"] as string, |
|||
id: json["id"] as string, |
|||
imageUrl: json["imageUrl"] as string, |
|||
lastName: json["lastName"] as string, |
|||
lastSeen: json["lastSeen"] as int?, |
|||
metadata: json["metadata"] as Dictionary<string, object>, |
|||
role: ChatRoomUtils.getRoleFromString(json["role"] as string) |
|||
); |
|||
/// Converts user to the map representation, encodable to JSON.
|
|||
} |
|||
|
|||
public Dictionary<string, object> toJson() |
|||
{ |
|||
return new Dictionary<string, object> |
|||
{ |
|||
{"createdAt", createdAt}, |
|||
{"firstName", firstName}, |
|||
{"id", id}, |
|||
{"imageUrl", imageUrl}, |
|||
{"lastName", lastName}, |
|||
{"lastSeen", lastSeen}, |
|||
{"metadata", metadata}, |
|||
{"role", ChatRoomUtils.toShortString(role == null ? null : role)} |
|||
}; |
|||
} |
|||
|
|||
/// Creates a copy of the user with an updated data.
|
|||
/// [firstName], [imageUrl], [lastName], [lastSeen] and [role] with
|
|||
/// null values will nullify existing values.
|
|||
/// [metadata] with null value will nullify existing metadata, otherwise
|
|||
/// both metadatas will be merged into one Map, where keys from a passed
|
|||
/// metadata will overwite keys from the previous one.
|
|||
private User copyWith( |
|||
string firstName = null, |
|||
string imageUrl = null, |
|||
string lastName = null, |
|||
int? lastSeen = null, |
|||
Dictionary<string, object> metadata = null, |
|||
Role? role = null |
|||
) |
|||
{ |
|||
var result = new Dictionary<string, object>(); |
|||
if (this.metadata != null) |
|||
foreach (var metaItem in this.metadata) |
|||
result.Add(metaItem.Key, metaItem.Value); |
|||
foreach (var metaItem in metadata) result.Add(metaItem.Key, metaItem.Value); |
|||
|
|||
|
|||
return new User( |
|||
firstName: firstName, |
|||
id: id, |
|||
imageUrl: imageUrl, |
|||
lastName: lastName, |
|||
lastSeen: lastSeen, |
|||
metadata: metadata == null |
|||
? null |
|||
: result, |
|||
role: role |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Unity.UIWidgets.foundation; |
|||
|
|||
namespace ChatComponents |
|||
{ |
|||
public static class ChatRoomUtils |
|||
{ |
|||
public static Status? getStatusFromString(string stringStatus) { |
|||
foreach( var status in Enum.GetValues(typeof(Status))) { |
|||
if (status.ToString() == stringStatus) { |
|||
return status is Status ? (Status) status : (Status?) null; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// Converts [stringRole] to the [Role] enum.
|
|||
public static Role? getRoleFromString(string stringRole) { |
|||
foreach( var role in Enum.GetValues(typeof(Role))) { |
|||
if (role.ToString() == stringRole) { |
|||
return role is Role ? (Role) role : (Role?) null; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// Converts [stringRoomType] to the [RoomType] enum.
|
|||
public static RoomType getRoomTypeFromString(string stringRoomType) { |
|||
foreach( var roomType in Enum.GetValues(typeof(RoomType))) { |
|||
if (roomType.ToString() == stringRoomType) { |
|||
return (RoomType) (roomType is RoomType ? (RoomType) roomType : (RoomType?) null); |
|||
} |
|||
} |
|||
|
|||
return RoomType.unsupported; |
|||
} |
|||
|
|||
public static string toShortString(object _object = null) |
|||
{ |
|||
return _object.ToString().Split('.').last(); |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
} |
|
|||
using System.Collections.Generic; |
|||
using JetBrains.Annotations; |
|||
using UIWidgetsSample; |
|||
|
|||
namespace ChatComponents |
|||
{ |
|||
public class PreviewData : Equatable |
|||
{ |
|||
/// Link description (usually og:description meta tag)
|
|||
public readonly string description; |
|||
|
|||
/// See [PreviewDataImage]
|
|||
public readonly PreviewDataImage image; |
|||
|
|||
/// Remote resource URL
|
|||
public readonly string link; |
|||
|
|||
/// Link title (usually og:title meta tag)
|
|||
public readonly string title; |
|||
|
|||
/// Creates preview data.
|
|||
public PreviewData( |
|||
string description = null, |
|||
[CanBeNull] PreviewDataImage image = null, |
|||
string link = null, |
|||
string title = null |
|||
) |
|||
{ |
|||
this.description = description; |
|||
this.image = image; |
|||
this.link = link; |
|||
this.title = title; |
|||
} |
|||
|
|||
/// Equatable props
|
|||
public override List<object> props => new List<object> |
|||
{description, image, link, title}; |
|||
|
|||
/// Creates preview data from a map (decoded JSON).
|
|||
public static PreviewData fromJson(Dictionary<string, object> json) |
|||
{ |
|||
return new PreviewData( |
|||
json["description"] as string, |
|||
json["image"] == null |
|||
? null |
|||
: PreviewDataImage.fromJson(json["image"] as Dictionary<string, object>), |
|||
json["link"] as string, |
|||
json["title"] as string |
|||
); |
|||
} |
|||
|
|||
/// Converts preview data to the map representation, encodable to JSON.
|
|||
public Dictionary<string, object> toJson() |
|||
{ |
|||
return new Dictionary<string, object> |
|||
{ |
|||
{"description", description}, |
|||
{"image", image?.toJson()}, |
|||
{"link", link}, |
|||
{"title", title} |
|||
}; |
|||
} |
|||
|
|||
/// Creates a copy of the preview data with an updated data.
|
|||
/// Null values will nullify existing values.
|
|||
private PreviewData copyWith( |
|||
string description = null, |
|||
PreviewDataImage image = null, |
|||
string link = null, |
|||
string title = null |
|||
) |
|||
{ |
|||
return new PreviewData( |
|||
description, |
|||
image, |
|||
link, |
|||
title |
|||
); |
|||
} |
|||
} |
|||
|
|||
/// A utility class that forces image"s width and height to be stored
|
|||
/// alongside the url.
|
|||
///
|
|||
/// See https://github.com/flyerhq/flutter_link_previewer
|
|||
public class PreviewDataImage : Equatable |
|||
{ |
|||
/// Image height in pixels
|
|||
public readonly float height; |
|||
|
|||
/// Remote image URL
|
|||
public readonly string url; |
|||
|
|||
/// Image width in pixels
|
|||
public readonly float width; |
|||
|
|||
/// Creates preview data image.
|
|||
public PreviewDataImage( |
|||
float height, |
|||
string url, |
|||
float width) |
|||
{ |
|||
this.height = height; |
|||
this.url = url; |
|||
this.width = width; |
|||
} |
|||
|
|||
/// Equatable props
|
|||
|
|||
public override List<object> props => new List<object> |
|||
{height, url, width}; |
|||
|
|||
/// Creates preview data image from a map (decoded JSON).
|
|||
public static PreviewDataImage fromJson(Dictionary<string, object> json) |
|||
{ |
|||
return new PreviewDataImage( |
|||
json["height"] is float ? (float) json["height"] : 0, |
|||
json["url"] as string, |
|||
json["width"] is float ? (float) json["width"] : 0 |
|||
); |
|||
} |
|||
|
|||
/// Converts preview data image to the map representation, encodable to JSON.
|
|||
public Dictionary<string, object> toJson() |
|||
{ |
|||
return new Dictionary<string, object> |
|||
{ |
|||
{"height", height}, |
|||
{"url", url}, |
|||
{"width", width} |
|||
}; |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace ChatComponents |
|||
{ |
|||
public class FileMessage : Message |
|||
{ |
|||
/// Creates a custom message.
|
|||
|
|||
|
|||
public readonly string mimeType; |
|||
public readonly string name; |
|||
public readonly int size; |
|||
public readonly string uri; |
|||
|
|||
public FileMessage( |
|||
User author, |
|||
string id, |
|||
string name, |
|||
int size, |
|||
string uri, |
|||
int? createdAt = null, |
|||
Dictionary<string, object> metadata = null, |
|||
string mimeType = null, |
|||
string roomId = null, |
|||
Status? status = default |
|||
) : base( |
|||
author, |
|||
createdAt, |
|||
id, |
|||
metadata, |
|||
roomId, |
|||
status, |
|||
MessageType.file |
|||
) |
|||
{ |
|||
this.name = name; |
|||
this.size = size; |
|||
this.uri = uri; |
|||
this.mimeType = mimeType; |
|||
} |
|||
|
|||
public override List<object> props => new List<object> |
|||
{ |
|||
author, |
|||
createdAt, |
|||
id, |
|||
metadata, |
|||
mimeType, |
|||
name, |
|||
roomId, |
|||
size, |
|||
status, |
|||
uri, |
|||
}; |
|||
|
|||
public FileMessage fromPartial( |
|||
User author, |
|||
string id, |
|||
PartialFile partialFile, |
|||
int? createdAt = null, |
|||
Dictionary<string, object> metadata = null, |
|||
string roomId = null, |
|||
Status? status = default |
|||
) |
|||
{ |
|||
return new FileMessage( |
|||
author : author, |
|||
createdAt:createdAt, |
|||
id:id, |
|||
metadata:metadata, |
|||
roomId:roomId, |
|||
status:status, |
|||
mimeType : partialFile.mimeType, |
|||
name : partialFile.name, |
|||
size : partialFile.size, |
|||
uri : partialFile.uri |
|||
); |
|||
|
|||
} |
|||
|
|||
/// Creates a custom message from a map (decoded JSON).
|
|||
public static FileMessage fromJson(Dictionary<string, object> json) |
|||
{ |
|||
var test = json["size"]; |
|||
return new FileMessage( |
|||
User.fromJson(json["author"] as Dictionary<string, object>), |
|||
createdAt: json["createdAt"] as int?, |
|||
id: json["id"] as string, |
|||
metadata: json["metadata"] as Dictionary<string, object>, |
|||
roomId: json["roomId"] as string, |
|||
status: ChatRoomUtils.getStatusFromString(json["status"] as string), |
|||
mimeType : json["mimeType"] as string, |
|||
name : json["name"] as string, |
|||
size : Convert.ToInt32(json["size"]) , |
|||
uri : json["uri"] as string |
|||
); |
|||
} |
|||
|
|||
/// Converts a custom message to the map representation,
|
|||
/// encodable to JSON.
|
|||
public override Dictionary<string, object> toJson() |
|||
{ |
|||
return new Dictionary<string, object> |
|||
{ |
|||
{"author", author.toJson()}, |
|||
{"createdAt", createdAt}, |
|||
{"id", id}, |
|||
{"metadata", metadata}, |
|||
{"mimeType", mimeType}, |
|||
{"name", name}, |
|||
{"roomId", roomId}, |
|||
{"size", size}, |
|||
{"status", ChatRoomUtils.toShortString(status)}, |
|||
{"type", ChatRoomUtils.toShortString(MessageType.file)}, |
|||
{"uri", uri} |
|||
}; |
|||
} |
|||
|
|||
/// Creates a copy of the custom message with an updated data.
|
|||
/// [metadata] with null value will nullify existing metadata, otherwise
|
|||
/// both metadatas will be merged into one Map, where keys from a passed
|
|||
/// metadata will overwite keys from the previous one.
|
|||
/// [previewData] is ignored for this message type.
|
|||
/// [status] with null value will be overwritten by the previous status.
|
|||
/// [text] is ignored for this message type.
|
|||
public override Message copyWith( |
|||
Dictionary<string, object> metadata = null, |
|||
PreviewData previewData = null, |
|||
Status? status = null, |
|||
string text = null |
|||
) |
|||
{ |
|||
var result = new Dictionary<string, object>(); |
|||
if (this.metadata != null) |
|||
foreach (var metaItem in this.metadata) |
|||
result.Add(metaItem.Key, metaItem.Value); |
|||
foreach (var metaItem in metadata) result.Add(metaItem.Key, metaItem.Value); |
|||
return new FileMessage( |
|||
author: author, |
|||
createdAt: createdAt, |
|||
id: id, |
|||
metadata: metadata == null |
|||
? null |
|||
: result, |
|||
mimeType: mimeType, |
|||
name: name, |
|||
roomId: roomId, |
|||
size: size, |
|||
status: status ?? this.status, |
|||
uri: uri |
|||
); |
|||
} |
|||
/// Equatable props
|
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace ChatComponents |
|||
{ |
|||
public class ImageMessage : Message |
|||
{ |
|||
|
|||
|
|||
/// Creates a custom message.
|
|||
public readonly string name; |
|||
|
|||
public readonly int size; |
|||
public readonly string uri; |
|||
public readonly float? width; |
|||
public readonly float? height; |
|||
|
|||
public ImageMessage( |
|||
User author, |
|||
string id, |
|||
string name, |
|||
int size, |
|||
string uri, |
|||
int? createdAt = null, |
|||
Dictionary<string, object> metadata = null, |
|||
string roomId = null, |
|||
Status? status = default, |
|||
float? height = null, |
|||
float? width = null |
|||
) : base( |
|||
author, |
|||
createdAt, |
|||
id, |
|||
metadata, |
|||
roomId, |
|||
status, |
|||
MessageType.image |
|||
) |
|||
{ |
|||
this.name = name; |
|||
this.size = size; |
|||
this.uri = uri; |
|||
this.height = height; |
|||
this.width = width; |
|||
} |
|||
|
|||
public override List<object> props => new List<object> |
|||
{ |
|||
author, |
|||
createdAt, |
|||
id, |
|||
metadata, |
|||
height, |
|||
width, |
|||
name, |
|||
roomId, |
|||
size, |
|||
status, |
|||
uri |
|||
}; |
|||
|
|||
public ImageMessage fromPartial( |
|||
User author, |
|||
string id, |
|||
PartialImage partialImage, |
|||
int? createdAt = null, |
|||
Dictionary<string, object> metadata = null, |
|||
string roomId = null, |
|||
Status? status = default |
|||
) |
|||
{ |
|||
return new ImageMessage( |
|||
author, |
|||
createdAt: createdAt, |
|||
id: id, |
|||
metadata: metadata, |
|||
roomId: roomId, |
|||
status: status, |
|||
name: partialImage.name, |
|||
size: partialImage.size, |
|||
uri: partialImage.uri, |
|||
height: partialImage.height, |
|||
width: partialImage.width |
|||
); |
|||
} |
|||
|
|||
/// Creates a custom message from a map (decoded JSON).
|
|||
public static ImageMessage fromJson(Dictionary<string, object> json) |
|||
{ |
|||
var test = json["size"]; |
|||
return new ImageMessage( |
|||
User.fromJson(json["author"] as Dictionary<string, object>), |
|||
createdAt: json["createdAt"] as int?, |
|||
id: json["id"] as string, |
|||
metadata: json["metadata"] as Dictionary<string, object>, |
|||
roomId: json["roomId"] as string, |
|||
status: ChatRoomUtils.getStatusFromString(json["status"] as string), |
|||
height: Convert.ToDouble(json["height"]) is float ? (float) Convert.ToDouble(json["height"]) : 0f, |
|||
width: Convert.ToDouble(json["width"]) is float ? (float) Convert.ToDouble(json["width"]) : 0f, |
|||
name: json["name"] as string, |
|||
size: Convert.ToInt32(json["size"]), |
|||
uri: json["uri"] as string |
|||
); |
|||
} |
|||
|
|||
/// Converts a custom message to the map representation,
|
|||
/// encodable to JSON.
|
|||
public override Dictionary<string, object> toJson() |
|||
{ |
|||
return new Dictionary<string, object> |
|||
{ |
|||
{"author", author.toJson()}, |
|||
{"createdAt", createdAt}, |
|||
{"id", id}, |
|||
{"metadata", metadata}, |
|||
{"name", name}, |
|||
{"roomId", roomId}, |
|||
{"size", size}, |
|||
{"status", ChatRoomUtils.toShortString(status)}, |
|||
{"type", ChatRoomUtils.toShortString(MessageType.file)}, |
|||
{"uri", uri}, |
|||
{"height", height}, |
|||
{"width", width} |
|||
}; |
|||
} |
|||
|
|||
/// Creates a copy of the custom message with an updated data.
|
|||
/// [metadata] with null value will nullify existing metadata, otherwise
|
|||
/// both metadatas will be merged into one Map, where keys from a passed
|
|||
/// metadata will overwite keys from the previous one.
|
|||
/// [previewData] is ignored for this message type.
|
|||
/// [status] with null value will be overwritten by the previous status.
|
|||
/// [text] is ignored for this message type.
|
|||
public override Message copyWith( |
|||
Dictionary<string, object> metadata = null, |
|||
PreviewData previewData = null, |
|||
Status? status = null, |
|||
string text = null |
|||
) |
|||
{ |
|||
var result = new Dictionary<string, object>(); |
|||
if (this.metadata != null) |
|||
foreach (var metaItem in this.metadata) |
|||
result.Add(metaItem.Key, metaItem.Value); |
|||
foreach (var metaItem in metadata) result.Add(metaItem.Key, metaItem.Value); |
|||
return new ImageMessage( |
|||
author, |
|||
createdAt: createdAt, |
|||
id: id, |
|||
metadata: metadata == null |
|||
? null |
|||
: result, |
|||
name: name, |
|||
roomId: roomId, |
|||
size: size, |
|||
status: status ?? this.status, |
|||
uri: uri, |
|||
height: height, |
|||
width: width |
|||
); |
|||
} |
|||
/// Equatable props
|
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace ChatComponents |
|||
{ |
|||
public class PartialFile |
|||
|
|||
|
|||
{ |
|||
/// Media type
|
|||
public readonly string mimeType; |
|||
|
|||
/// The name of the file
|
|||
public readonly string name; |
|||
|
|||
/// Size of the file in bytes
|
|||
public readonly int size; |
|||
|
|||
/// The file source (either a remote URL or a local resource)
|
|||
public readonly string uri; |
|||
|
|||
/// Creates a partial file message with all variables file can have.
|
|||
/// Use [FileMessage] to create a full message.
|
|||
/// You can use [FileMessage.fromPartial] constructor to create a full
|
|||
/// message from a partial one.
|
|||
public PartialFile( |
|||
string name, |
|||
int size, |
|||
string uri, |
|||
string mimeType = null |
|||
) |
|||
{ |
|||
this.name = name; |
|||
this.size = size; |
|||
this.uri = uri; |
|||
this.mimeType = mimeType; |
|||
} |
|||
|
|||
/// Creates a partial file message from a map (decoded JSON).
|
|||
public PartialFile fromJson(Dictionary<string, object> json) |
|||
{ |
|||
return new PartialFile( |
|||
mimeType: json["mimeType"] as string, |
|||
name: json["name"] as string, |
|||
size: Convert.ToInt32(json["size"]), |
|||
uri: json["uri"] as string |
|||
); |
|||
} |
|||
|
|||
/// Converts a partial file message to the map representation, encodable to JSON.
|
|||
private Dictionary<string, object> toJson() |
|||
{ |
|||
return new Dictionary<string, object> |
|||
{ |
|||
{"mimeType", mimeType}, |
|||
{"name", name}, |
|||
{"size", size}, |
|||
{"uri", uri} |
|||
}; |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace ChatComponents |
|||
{ |
|||
public class PartialImage |
|||
{ |
|||
|
|||
/// The name of the file
|
|||
public readonly string name; |
|||
|
|||
/// Size of the file in bytes
|
|||
public readonly int size; |
|||
|
|||
/// The file source (either a remote URL or a local resource)
|
|||
public readonly string uri; |
|||
|
|||
public readonly float? width; |
|||
public readonly float? height; |
|||
|
|||
|
|||
/// Creates a partial file message with all variables file can have.
|
|||
/// Use [FileMessage] to create a full message.
|
|||
/// You can use [FileMessage.fromPartial] constructor to create a full
|
|||
/// message from a partial one.
|
|||
public PartialImage( |
|||
string name, |
|||
int size, |
|||
string uri, |
|||
float? height = null, |
|||
float? width = null |
|||
|
|||
) |
|||
{ |
|||
this.name = name; |
|||
this.size = size; |
|||
this.uri = uri; |
|||
this.height = height; |
|||
this.width = width; |
|||
|
|||
} |
|||
|
|||
/// Creates a partial file message from a map (decoded JSON).
|
|||
public PartialImage fromJson(Dictionary<string, object> json) |
|||
{ |
|||
return new PartialImage( |
|||
|
|||
name: json["name"] as string, |
|||
size: Convert.ToInt32(json["size"]), |
|||
uri: json["uri"] as string, |
|||
height: Convert.ToDouble(json["height"]) is float ? (float) Convert.ToDouble(json["height"]) : 0f, |
|||
width: Convert.ToDouble(json["width"]) is float ? (float) Convert.ToDouble(json["width"]) : 0f |
|||
); |
|||
} |
|||
|
|||
/// Converts a partial file message to the map representation, encodable to JSON.
|
|||
private Dictionary<string, object> toJson() |
|||
{ |
|||
return new Dictionary<string, object> |
|||
{ |
|||
|
|||
{"name", name}, |
|||
{"size", size}, |
|||
{"uri", uri}, |
|||
{"height", height}, |
|||
{"width", width} |
|||
}; |
|||
} |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace ChatComponents |
|||
{ |
|||
public class PartialText |
|||
{ |
|||
/// User's message
|
|||
public readonly string text; |
|||
|
|||
/// Creates a partial text message with all variables text can have.
|
|||
/// Use [TextMessage] to create a full message.
|
|||
/// You can use [TextMessage.fromPartial] constructor to create a full
|
|||
/// message from a partial one.
|
|||
public PartialText( |
|||
string text |
|||
) |
|||
{ |
|||
this.text = text; |
|||
} |
|||
|
|||
/// Creates a partial text message from a map (decoded JSON).
|
|||
private PartialText fromJson(Dictionary<string, object> json) |
|||
{ |
|||
return new PartialText(json["text"] as string); |
|||
} |
|||
|
|||
/// Converts a partial text message to the map representation, encodable to JSON.
|
|||
private Dictionary<string, object> toJson() |
|||
{ |
|||
return new Dictionary<string, object> |
|||
{ |
|||
{"text", text} |
|||
}; |
|||
} |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace ChatComponents |
|||
{ |
|||
public class TextMessage : Message |
|||
{ |
|||
/// See [PreviewData]
|
|||
public readonly PreviewData previewData; |
|||
|
|||
/// User"s message
|
|||
public readonly string text; |
|||
|
|||
/// Creates a text message.
|
|||
public TextMessage( |
|||
User author, |
|||
string id, |
|||
int? createdAt = null, |
|||
Dictionary<string, object> metadata = null, |
|||
string roomId = null, |
|||
Status? status = default, |
|||
PreviewData previewData = null, |
|||
string text = null |
|||
) : base( |
|||
author, |
|||
createdAt, |
|||
id, |
|||
metadata, |
|||
roomId, |
|||
status, |
|||
MessageType.text |
|||
) |
|||
{ |
|||
this.previewData = previewData; |
|||
this.text = text; |
|||
} |
|||
|
|||
|
|||
public override List<object> props => new List<object> |
|||
{ |
|||
author, createdAt, id, metadata, previewData, roomId, status, text |
|||
}; |
|||
|
|||
/// Creates a full text message from a partial one.
|
|||
private TextMessage fromPartial( |
|||
User author, |
|||
string id, |
|||
PartialText partialText, |
|||
int? createdAt = null, |
|||
Dictionary<string, object> metadata = null, |
|||
string roomId = null, |
|||
Status? status = default |
|||
) |
|||
{ |
|||
return new TextMessage( |
|||
author, |
|||
createdAt: createdAt, |
|||
id: id, |
|||
metadata: metadata, |
|||
roomId: roomId, |
|||
status: status, |
|||
previewData: null, |
|||
text: partialText.text); |
|||
} |
|||
|
|||
/// Creates a text message from a map (decoded JSON).
|
|||
private TextMessage fromJson(Dictionary<string, object> json) |
|||
{ |
|||
var test = json["size"]; |
|||
return new TextMessage( |
|||
User.fromJson(json["author"] as Dictionary<string, object>), |
|||
createdAt: json["createdAt"] as int?, |
|||
id: json["id"] as string, |
|||
metadata: json["metadata"] as Dictionary<string, object>, |
|||
roomId: json["roomId"] as string, |
|||
status: ChatRoomUtils.getStatusFromString(json["status"] as string), |
|||
previewData: json["previewData"] == null |
|||
? null |
|||
: PreviewData.fromJson(json["previewData"] as Dictionary<string, object>), |
|||
text: json["text"] as string |
|||
); |
|||
} |
|||
|
|||
public override Dictionary<string, object> toJson() |
|||
{ |
|||
return new Dictionary<string, object> |
|||
{ |
|||
{"author", author.toJson()}, |
|||
{"createdAt", createdAt}, |
|||
{"id", id}, |
|||
{"metadata", metadata}, |
|||
{"roomId", roomId}, |
|||
{"previewData", previewData?.toJson()}, |
|||
{"status", ChatRoomUtils.toShortString(status)}, |
|||
{"type", ChatRoomUtils.toShortString(MessageType.file)}, |
|||
{"text", text} |
|||
}; |
|||
} |
|||
|
|||
/// Converts a text message to the map representation, encodable to JSON.
|
|||
/// Creates a copy of the text message with an updated data
|
|||
/// [metadata] with null value will nullify existing metadata, otherwise
|
|||
/// both metadatas will be merged into one Map, where keys from a passed
|
|||
/// metadata will overwite keys from the previous one.
|
|||
/// [status] with null value will be overwritten by the previous status.
|
|||
public override Message copyWith( |
|||
Dictionary<string, object> metadata = null, |
|||
PreviewData previewData = null, |
|||
Status? status = null, |
|||
string text = null |
|||
) |
|||
{ |
|||
var result = new Dictionary<string, object>(); |
|||
if (this.metadata != null) |
|||
foreach (var metaItem in this.metadata) |
|||
result.Add(metaItem.Key, metaItem.Value); |
|||
foreach (var metaItem in metadata) result.Add(metaItem.Key, metaItem.Value); |
|||
return new TextMessage( |
|||
author, |
|||
createdAt: createdAt, |
|||
id: id, |
|||
metadata: metadata == null |
|||
? null |
|||
: result, |
|||
previewData: previewData, |
|||
roomId: roomId, |
|||
text: text ?? this.text, |
|||
status: status ?? this.status |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace ChatComponents |
|||
{ |
|||
public class UnsupportedMessage : Message |
|||
{ |
|||
|
|||
|
|||
/// Creates a text message.
|
|||
public UnsupportedMessage( |
|||
User author, |
|||
string id, |
|||
int? createdAt = null, |
|||
Dictionary<string, object> metadata = null, |
|||
string roomId = null, |
|||
Status? status = default |
|||
|
|||
) : base( |
|||
author, |
|||
createdAt, |
|||
id, |
|||
metadata, |
|||
roomId, |
|||
status, |
|||
MessageType.unsupported |
|||
) |
|||
{ |
|||
|
|||
} |
|||
|
|||
|
|||
public override List<object> props => new List<object> |
|||
{ |
|||
author, createdAt, id, metadata, roomId, status |
|||
}; |
|||
|
|||
|
|||
|
|||
|
|||
/// Creates a text message from a map (decoded JSON).
|
|||
private UnsupportedMessage fromJson(Dictionary<string, object> json) |
|||
{ |
|||
var test = json["size"]; |
|||
return new UnsupportedMessage( |
|||
User.fromJson(json["author"] as Dictionary<string, object>), |
|||
createdAt: json["createdAt"] as int?, |
|||
id: json["id"] as string, |
|||
metadata: json["metadata"] as Dictionary<string, object>, |
|||
roomId: json["roomId"] as string, |
|||
status: ChatRoomUtils.getStatusFromString(json["status"] as string) |
|||
|
|||
); |
|||
} |
|||
|
|||
public override Dictionary<string, object> toJson() |
|||
{ |
|||
return new Dictionary<string, object> |
|||
{ |
|||
{"author", author.toJson()}, |
|||
{"createdAt", createdAt}, |
|||
{"id", id}, |
|||
{"metadata", metadata}, |
|||
{"roomId", roomId}, |
|||
|
|||
{"status", ChatRoomUtils.toShortString(status)}, |
|||
{"type", ChatRoomUtils.toShortString(MessageType.file)}, |
|||
|
|||
}; |
|||
} |
|||
|
|||
/// Converts a text message to the map representation, encodable to JSON.
|
|||
/// Creates a copy of the text message with an updated data
|
|||
/// [metadata] with null value will nullify existing metadata, otherwise
|
|||
/// both metadatas will be merged into one Map, where keys from a passed
|
|||
/// metadata will overwite keys from the previous one.
|
|||
/// [status] with null value will be overwritten by the previous status.
|
|||
public override Message copyWith( |
|||
Dictionary<string, object> metadata = null, |
|||
PreviewData previewData = null, |
|||
Status? status = null, |
|||
string text = null |
|||
) |
|||
{ |
|||
var result = new Dictionary<string, object>(); |
|||
if (this.metadata != null) |
|||
foreach (var metaItem in this.metadata) |
|||
result.Add(metaItem.Key, metaItem.Value); |
|||
foreach (var metaItem in metadata) result.Add(metaItem.Key, metaItem.Value); |
|||
return new UnsupportedMessage( |
|||
author, |
|||
createdAt: createdAt, |
|||
id: id, |
|||
metadata: metadata == null |
|||
? null |
|||
: result, |
|||
|
|||
roomId: roomId, |
|||
status: status ?? this.status |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace ChatComponents |
|||
{ |
|||
public class CustomMessage : Message |
|||
{ |
|||
/// Creates a custom message.
|
|||
public CustomMessage( |
|||
User author, |
|||
string id, |
|||
int? createdAt = null, |
|||
Dictionary<string, object> metadata = null, |
|||
string roomId = null, |
|||
Status? status = default |
|||
) : base( |
|||
author, |
|||
createdAt, |
|||
id, |
|||
metadata, |
|||
roomId, |
|||
status, |
|||
MessageType.custom |
|||
) |
|||
{ |
|||
} |
|||
|
|||
public override List<object> props => new List<object> |
|||
{ |
|||
author, |
|||
createdAt, |
|||
id, |
|||
metadata, |
|||
roomId, |
|||
status |
|||
}; |
|||
|
|||
/// Creates a custom message from a map (decoded JSON).
|
|||
public static CustomMessage fromJson(Dictionary<string, object> json) |
|||
{ |
|||
return new CustomMessage( |
|||
User.fromJson(json["author"] as Dictionary<string, object>), |
|||
createdAt: json["createdAt"] as int?, |
|||
id: json["id"] as string, |
|||
metadata: json["metadata"] as Dictionary<string, object>, |
|||
roomId: json["roomId"] as string, |
|||
status: ChatRoomUtils.getStatusFromString(json["status"] as string)); |
|||
} |
|||
|
|||
/// Converts a custom message to the map representation,
|
|||
/// encodable to JSON.
|
|||
public override Dictionary<string, object> toJson() |
|||
{ |
|||
return new Dictionary<string, object> |
|||
{ |
|||
{"author", author.toJson()}, |
|||
{"createdAt", createdAt}, |
|||
{"id", id}, |
|||
{"metadata", metadata}, |
|||
{"roomId", roomId}, |
|||
{"status", ChatRoomUtils.toShortString(status)}, |
|||
{"type", ChatRoomUtils.toShortString(type)} |
|||
}; |
|||
} |
|||
|
|||
/// Creates a copy of the custom message with an updated data.
|
|||
/// [metadata] with null value will nullify existing metadata, otherwise
|
|||
/// both metadatas will be merged into one Map, where keys from a passed
|
|||
/// metadata will overwite keys from the previous one.
|
|||
/// [previewData] is ignored for this message type.
|
|||
/// [status] with null value will be overwritten by the previous status.
|
|||
/// [text] is ignored for this message type.
|
|||
public override Message copyWith( |
|||
Dictionary<string, object> metadata = null, |
|||
PreviewData previewData = null, |
|||
Status? status = null, |
|||
string text = null |
|||
) |
|||
{ |
|||
var result = new Dictionary<string, object>(); |
|||
if (this.metadata != null) |
|||
foreach (var metaItem in this.metadata) |
|||
result.Add(metaItem.Key, metaItem.Value); |
|||
foreach (var metaItem in metadata) result.Add(metaItem.Key, metaItem.Value); |
|||
return new CustomMessage( |
|||
author, |
|||
createdAt: createdAt, |
|||
id: id, |
|||
metadata: metadata == null |
|||
? null |
|||
: result, |
|||
roomId: roomId, |
|||
status: status ?? this.status |
|||
); |
|||
} |
|||
/// Equatable props
|
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using ChatComponents; |
|||
using uiwidgets; |
|||
using Unity.UIWidgets.animation; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using Image = Unity.UIWidgets.widgets.Image; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public abstract class Conditional |
|||
{ |
|||
/// Creates a new platform appropriate conditional.
|
|||
///
|
|||
/// Creates an `IOConditional` if `dart:io` is available and a `BrowserConditional` if
|
|||
/// `dart:html` is available, otherwise it will throw an unsupported error.
|
|||
public Conditional() => createConditional(); |
|||
|
|||
/// Returns an appropriate platform ImageProvider for specified URI
|
|||
public abstract ImageProvider getProvider(string uri); |
|||
|
|||
public abstract BaseConditional createConditional(); |
|||
} |
|||
|
|||
public abstract class BaseConditional : Conditional |
|||
{ |
|||
public override BaseConditional createConditional() => new BrowserConditional(); |
|||
/*BaseConditional createConditional() => |
|||
throw UnsupportedError('Cannot create a conditional');*/ |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
/// A conditional for browser
|
|||
class BrowserConditional : BaseConditional |
|||
{ |
|||
/// Returns [NetworkImage] if URI starts with http
|
|||
/// otherwise returns transparent image
|
|||
public override ImageProvider getProvider(string uri) |
|||
{ |
|||
if ( uri.StartsWith("http") || uri.StartsWith("blob")) { |
|||
return new NetworkImage(uri); |
|||
} |
|||
else { |
|||
return new MemoryImage(ConditionalUtils.kTransparentImage); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static class ConditionalUtils |
|||
{ |
|||
/// Transparent image data
|
|||
public static byte[] kTransparentImage = nums.SelectMany(BitConverter.GetBytes).ToArray(); |
|||
|
|||
public static List<Int32> nums = new List<Int32>() |
|||
{ |
|||
0x89, |
|||
0x50, |
|||
0x4E, |
|||
0x47, |
|||
0x0D, |
|||
0x0A, |
|||
0x1A, |
|||
0x0A, |
|||
0x00, |
|||
0x00, |
|||
0x00, |
|||
0x0D, |
|||
0x49, |
|||
0x48, |
|||
0x44, |
|||
0x52, |
|||
0x00, |
|||
0x00, |
|||
0x00, |
|||
0x01, |
|||
0x00, |
|||
0x00, |
|||
0x00, |
|||
0x01, |
|||
0x08, |
|||
0x06, |
|||
0x00, |
|||
0x00, |
|||
0x00, |
|||
0x1F, |
|||
0x15, |
|||
0xC4, |
|||
0x89, |
|||
0x00, |
|||
0x00, |
|||
0x00, |
|||
0x0A, |
|||
0x49, |
|||
0x44, |
|||
0x41, |
|||
0x54, |
|||
0x78, |
|||
0x9C, |
|||
0x63, |
|||
0x00, |
|||
0x01, |
|||
0x00, |
|||
0x00, |
|||
0x05, |
|||
0x00, |
|||
0x01, |
|||
0x0D, |
|||
0x0A, |
|||
0x2D, |
|||
0xB4, |
|||
0x00, |
|||
0x00, |
|||
0x00, |
|||
0x00, |
|||
0x49, |
|||
0x45, |
|||
0x4E, |
|||
0x44, |
|||
0xAE, |
|||
}; |
|||
|
|||
|
|||
|
|||
} |
|||
|
|||
|
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using Unity.UIWidgets.foundation; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public abstract class Equatable : IEquatable<Equatable> |
|||
{ |
|||
/// {@macro equatable}
|
|||
public Equatable() |
|||
{ |
|||
} |
|||
|
|||
|
|||
/// {@template equatable_props}
|
|||
/// The list of properties that will be used to determine whether
|
|||
/// two instances are equal.
|
|||
/// {@endtemplate}
|
|||
public abstract List<Object> props |
|||
|
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// {@template equatable_stringify}
|
|||
/// If set to `true`, the [toString] method will be overridden to output
|
|||
/// this instance's [props].
|
|||
///
|
|||
/// A global default value for [stringify] can be set using
|
|||
/// `EquatableConfig.stringify`.
|
|||
///
|
|||
/// If this instance's [stringify] is set to null, the value of
|
|||
/// `EquatableConfig.stringify` will be used instead. This defaults to
|
|||
/// `false`.
|
|||
/// {@endtemplate}
|
|||
// ignore: avoid_returning_null
|
|||
bool? stringify |
|||
{ |
|||
get |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
public static bool operator ==(Equatable left,Equatable right) |
|||
{ |
|||
return left is Equatable && Equals(left, right) && Equals(left.props,right.props); |
|||
} |
|||
|
|||
public static bool operator !=(Equatable left, Equatable right) |
|||
{ |
|||
return !(left == right); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
/* public override int GetHashCode() { |
|||
return (this.GetType().GetHashCode()) ^ mapPropsToHashCode(props); |
|||
}*/ // ????
|
|||
|
|||
public override string ToString() { |
|||
switch (stringify) { |
|||
case true: |
|||
return EquatableUtils.mapPropsToString(this.GetType(), props); |
|||
case false: |
|||
return this.GetType().ToString(); |
|||
default: |
|||
return EquatableConfig.stringify == true |
|||
? EquatableUtils.mapPropsToString(this.GetType(), props) |
|||
: this.GetType().ToString(); |
|||
} |
|||
} |
|||
|
|||
public bool Equals(Equatable other) |
|||
{ |
|||
if (ReferenceEquals(null, other)) return false; |
|||
if (ReferenceEquals(this, other)) return true; |
|||
return Equals(props, other.props); |
|||
} |
|||
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (ReferenceEquals(null, obj)) return false; |
|||
if (ReferenceEquals(this, obj)) return true; |
|||
if (obj.GetType() != this.GetType()) return false; |
|||
return Equals((Equatable) obj); |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
return (props != null ? props.GetHashCode() : 0); |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|
|||
using Unity.UIWidgets.foundation; |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using Unity.UIWidgets.foundation; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
class EquatableConfig |
|||
{ |
|||
/// {@template stringify}
|
|||
/// Global [stringify] setting for all [Equatable] instances.
|
|||
///
|
|||
/// If [stringify] is overridden for a particular [Equatable] instance,
|
|||
/// then the local [stringify] value takes precedence
|
|||
/// over [EquatableConfig.stringify].
|
|||
///
|
|||
/// This value defaults to true in debug mode and false in release mode.
|
|||
/// {@endtemplate}
|
|||
public static bool stringify |
|||
{ |
|||
get |
|||
{ |
|||
if (_stringify == null) |
|||
{ |
|||
D.assert(() => |
|||
{ |
|||
_stringify = true; |
|||
return true; |
|||
}); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
set { _stringify = value; } |
|||
} |
|||
|
|||
/// {@macro stringify}
|
|||
|
|||
|
|||
static bool? _stringify; |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using Unity.UIWidgets.foundation; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
|
|||
public abstract class EquatableMixin: IEquatable<EquatableMixin> { |
|||
/// {@macro equatable_props}
|
|||
private List<object> props |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// {@macro equatable_stringify}
|
|||
// ignore: avoid_returning_null
|
|||
bool? stringify |
|||
{ |
|||
get |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public static bool operator ==(EquatableMixin left,EquatableMixin right) { |
|||
return Equals(left, right) && Equals(left.props, right.props); |
|||
} |
|||
|
|||
public static bool operator !=(EquatableMixin left, EquatableMixin right) |
|||
{ |
|||
return !(left == right); |
|||
} |
|||
|
|||
//@override int get hashCode => runtimeType.hashCode ^ mapPropsToHashCode(props);
|
|||
|
|||
public override string ToString() { |
|||
switch (stringify) { |
|||
case true: |
|||
return EquatableUtils.mapPropsToString(this.GetType(), props); |
|||
case false: |
|||
return this.GetType().ToString(); |
|||
default: |
|||
return EquatableConfig.stringify == true |
|||
? EquatableUtils.mapPropsToString(this.GetType(), props) |
|||
: this.GetType().ToString(); |
|||
} |
|||
} |
|||
|
|||
public bool Equals(EquatableMixin other) |
|||
{ |
|||
if (ReferenceEquals(null, other)) return false; |
|||
if (ReferenceEquals(this, other)) return true; |
|||
return Equals(props, other.props); |
|||
} |
|||
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (ReferenceEquals(null, obj)) return false; |
|||
if (ReferenceEquals(this, obj)) return true; |
|||
if (obj.GetType() != this.GetType()) return false; |
|||
return Equals((EquatableMixin) obj); |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
return (props != null ? props.GetHashCode() : 0); |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public class EquatableUtils |
|||
{ |
|||
// private DeepCollectionEquality _equality = DeepCollectionEquality();
|
|||
|
|||
private int mapPropsToHashCode(List<object> props) |
|||
{ |
|||
var value = 0; |
|||
foreach (var prop in props) |
|||
{ |
|||
value = _combine(value, prop); |
|||
} |
|||
return _finish(props == null ? 0 : value); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
/// Determines whether [list1] and [list2] are equal.
|
|||
private bool equals(List<object> list1, List<object> list2) |
|||
{ |
|||
if (list1.Equals(list2)) return true; |
|||
if (list1 == null || list2 == null) return false; |
|||
var length = list1.Count; |
|||
if (length != list2.Count) return false; |
|||
|
|||
for (var i = 0; i < length; i++) |
|||
{ |
|||
var unit1 = list1[i]; |
|||
var unit2 = list2[i]; |
|||
|
|||
if (_isEquatable(unit1) && _isEquatable(unit2)) |
|||
{ |
|||
if (unit1 != unit2) return false; |
|||
} |
|||
else if (unit1 is List<object> || unit1 is Dictionary<object, object>) |
|||
{ |
|||
//if (!_equality.equals(unit1, unit2))
|
|||
if(!Equals(unit1,unit2)) |
|||
return false; |
|||
} |
|||
else if (unit1?.GetType() != unit2?.GetType()) |
|||
{ |
|||
return false; |
|||
} |
|||
else if (unit1 != unit2) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private bool _isEquatable(object _object) |
|||
{ |
|||
return _object is Equatable || _object is EquatableMixin; |
|||
} |
|||
|
|||
/// Jenkins Hash Functions
|
|||
/// https://en.wikipedia.org/wiki/Jenkins_hash_function
|
|||
private int _combine(int hash, object _object) |
|||
{ |
|||
if (_object is Dictionary<object, object>) |
|||
{ |
|||
var Keys = new List<object>(); |
|||
foreach (var key in ((Dictionary<object, object>) _object).Keys) Keys.Add(key); |
|||
Keys.Sort((a, b) => a.GetHashCode() - b.GetHashCode()); |
|||
foreach (var key in Keys) |
|||
{ |
|||
var value = (object) null; |
|||
var keyValue = ((Dictionary<object, object>) _object).TryGetValue(key, out value); |
|||
hash = hash ^ _combine(hash, new Dictionary<object, object> {{key, value}}); |
|||
} |
|||
|
|||
; |
|||
return hash; |
|||
} |
|||
|
|||
if (_object is List<object>) |
|||
{ |
|||
foreach (var value in (List<object>) _object) hash = hash ^ _combine(hash, value); |
|||
|
|||
return hash ^ ((List<object>) _object).Count; |
|||
} |
|||
|
|||
hash = 0x1fffffff & (hash + _object.GetHashCode()); |
|||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); |
|||
return hash ^ (hash >> 6); |
|||
} |
|||
|
|||
private int _finish(int hash) |
|||
{ |
|||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); |
|||
hash = hash ^ (hash >> 11); |
|||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); |
|||
} |
|||
|
|||
/// Returns a string for [props].
|
|||
public static string mapPropsToString(Type runtimeType, List<object> props) |
|||
{ |
|||
var result = ""; |
|||
var results = new List<string>(); |
|||
foreach (var prop in props) results.Add(prop.ToString()); |
|||
//props.map((prop) => prop.toString()).join(', ')
|
|||
return string.Join(",", results); |
|||
} |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public class DateHeader : Equatable |
|||
{ |
|||
/// Text to show in a header
|
|||
public readonly string text; |
|||
|
|||
/// Creates a date header.
|
|||
public DateHeader(string text = null) |
|||
{ |
|||
this.text = text; |
|||
} |
|||
|
|||
/// Equatable props
|
|||
public override List<object> props => new List<object> |
|||
{text}; |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public class MessageSpacer : Equatable |
|||
{ |
|||
/// Text to show in a header
|
|||
public readonly string id; |
|||
|
|||
public readonly float height; |
|||
|
|||
/// Creates a date header.
|
|||
public MessageSpacer( |
|||
float height = 0.0f, |
|||
string id = null) |
|||
{ |
|||
this.id = id; |
|||
this.height = height; |
|||
} |
|||
|
|||
/// Equatable props
|
|||
public override List<object> props => new List<object> |
|||
{id,height}; |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using Unity.UIWidgets.foundation; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public class PreviewImage : Equatable { |
|||
/// Creates a preview image.
|
|||
public PreviewImage( |
|||
string id, |
|||
string uri) |
|||
{ |
|||
this.id = id; |
|||
this.uri = uri; |
|||
} |
|||
|
|||
|
|||
public override List<object> props => new List<object>{id, uri}; |
|||
|
|||
/// Unique ID of the image
|
|||
public readonly string id; |
|||
|
|||
/// Image's URI
|
|||
public readonly string uri; |
|||
} |
|||
} |
|
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using UnityEngine; |
|||
using Image = Unity.UIWidgets.widgets.Image; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public delegate void OnPressed(); |
|||
|
|||
internal class AttachmentButton : StatelessWidget |
|||
{ |
|||
/// Callback for attachment button tap event
|
|||
public readonly OnPressed onPressed; |
|||
|
|||
/// Creates attachment button widget
|
|||
public AttachmentButton( |
|||
OnPressed onPressed, |
|||
Key key = null |
|||
) : base(key) |
|||
{ |
|||
this.onPressed = onPressed; |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
var test = InheritedChatTheme.of(context).theme.inputTextColor; |
|||
return new Container( |
|||
height: 14, |
|||
margin: EdgeInsets.only(right: 16), |
|||
width: 24, |
|||
child: new IconButton( |
|||
icon: InheritedChatTheme.of(context).theme.attachmentButtonIcon != null |
|||
? InheritedChatTheme.of(context).theme.attachmentButtonIcon |
|||
: Image.file( |
|||
"assets/icon-attachment.png", |
|||
color: InheritedChatTheme.of(context).theme.inputTextColor, |
|||
//package: "flutter_chat_ui",
|
|||
scale: 1 |
|||
), |
|||
onPressed: ()=> |
|||
{ |
|||
Debug.Log("add file , not available yet"); |
|||
//onPressed();
|
|||
}, |
|||
padding: EdgeInsets.zero, |
|||
tooltip: |
|||
InheritedL10n.of(context)?.l10n?.attachmentButtonAccessibilityLabel |
|||
) |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using ChatComponents; |
|||
using uiwidgets; |
|||
using Unity.UIWidgets.async; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using UnityEngine; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public delegate string CustomDateHeaderText(DateTime dateTime); |
|||
|
|||
public delegate void OnAttachmentPressed(); |
|||
|
|||
public delegate Future OnEndReached(); |
|||
|
|||
public delegate void OnMessageLongPress(ChatComponents.Message message); |
|||
|
|||
public delegate void OnMessageTap(ChatComponents.Message message); |
|||
|
|||
public delegate void OnPreviewDataFetched( PreviewData previewData,ChatComponents.TextMessage textMessage = null); |
|||
|
|||
public delegate void OnSendPressed(PartialText partialText); |
|||
|
|||
public delegate void OnTextChanged(string _str); |
|||
|
|||
public delegate Widget BuildCustomMessage(ChatComponents.Message message); |
|||
|
|||
public class Chat : StatefulWidget |
|||
{ |
|||
/// See [Message.buildCustomMessage]
|
|||
/// If [dateFormat], [dateLocale] and/or [timeFormat] is not enough to
|
|||
/// customize date headers in your case, use this to return an arbitrary
|
|||
/// string based on a [DateTime] of a particular message. Can be helpful to
|
|||
/// return "Today" if [DateTime] is today. IMPORTANT: this will replace
|
|||
/// all default date headers, so you must handle all cases yourself, like
|
|||
/// for example today, yesterday and before. Or you can just return the same
|
|||
/// date header for any message.
|
|||
public readonly BuildCustomMessage buildCustomMessage; |
|||
|
|||
public readonly CustomDateHeaderText customDateHeaderText; |
|||
|
|||
/// Allows you to customize the date format. IMPORTANT: only for the date,
|
|||
/// do not return time here. See [timeFormat] to customize the time format.
|
|||
/// [dateLocale] will be ignored if you use this, so if you want a localized date
|
|||
/// make sure you initialize your [DateFormat] with a locale. See [customDateHeaderText]
|
|||
/// for more customization.
|
|||
public readonly DateTime? dateFormat; |
|||
|
|||
/// Locale will be passed to the `Intl` package. Make sure you initialized
|
|||
/// date formatting in your app before passing any locale here, otherwise
|
|||
/// an error will be thrown. Also see [customDateHeaderText], [dateFormat], [timeFormat].
|
|||
public readonly string dateLocale; |
|||
|
|||
/// Disable automatic image preview on tap.
|
|||
public readonly bool? disableImageGallery; |
|||
|
|||
/// See [Input.isAttachmentUploading]
|
|||
public readonly bool? isAttachmentUploading; |
|||
|
|||
/// See [ChatList.isLastPage]
|
|||
public readonly bool? isLastPage; |
|||
|
|||
/// Localized copy. Extend [ChatL10n] class to create your own copy or use
|
|||
/// existing one, like the default [ChatL10nEn]. You can customize only
|
|||
/// certain variables, see more here [ChatL10nEn].
|
|||
public readonly ChatL10n l10n; |
|||
|
|||
/// List of [types.Message] to render in the chat widget
|
|||
public readonly List<ChatComponents.Message> messages; |
|||
|
|||
/// See [Input.onAttachmentPressed]
|
|||
public readonly OnAttachmentPressed onAttachmentPressed; |
|||
|
|||
/// See [ChatList.onEndReached]
|
|||
public readonly OnEndReached onEndReached; |
|||
|
|||
/// See [ChatList.onEndReachedThreshold]
|
|||
public readonly float? onEndReachedThreshold; |
|||
|
|||
/// See [Message.onMessageLongPress]
|
|||
public readonly OnMessageLongPress onMessageLongPress; |
|||
|
|||
/// See [Message.onMessageTap]
|
|||
public readonly OnMessageTap onMessageTap; |
|||
|
|||
/// See [Message.onPreviewDataFetched]
|
|||
public readonly OnPreviewDataFetched onPreviewDataFetched; |
|||
|
|||
/// See [Input.onSendPressed]
|
|||
public readonly OnSendPressed onSendPressed; |
|||
|
|||
/// See [Input.onTextChanged]
|
|||
public readonly OnTextChanged onTextChanged; |
|||
|
|||
/// See [Message.showUserAvatars]
|
|||
public readonly bool showUserAvatars; |
|||
|
|||
/// Show user names for received messages. Useful for a group chat. Will be
|
|||
/// shown only on text messages.
|
|||
public readonly bool showUserNames; |
|||
|
|||
/// Chat theme. Extend [ChatTheme] class to create your own theme or use
|
|||
/// existing one, like the [DefaultChatTheme]. You can customize only certain
|
|||
/// variables, see more here [DefaultChatTheme].
|
|||
public readonly ChatTheme theme; |
|||
|
|||
/// Allows you to customize the time format. IMPORTANT: only for the time,
|
|||
/// do not return date here. See [dateFormat] to customize the date format.
|
|||
/// [dateLocale] will be ignored if you use this, so if you want a localized time
|
|||
/// make sure you initialize your [DateFormat] with a locale. See [customDateHeaderText]
|
|||
/// for more customization.
|
|||
public readonly DateTime? timeFormat; |
|||
|
|||
/// See [Message.usePreviewData]
|
|||
public readonly bool usePreviewData; |
|||
|
|||
/// See [InheritedUser.user]
|
|||
public readonly User user; |
|||
|
|||
/// Creates a chat widget
|
|||
public Chat( |
|||
List<ChatComponents.Message> messages, |
|||
OnSendPressed onSendPressed, |
|||
User user, |
|||
Key key = null, |
|||
BuildCustomMessage buildCustomMessage = null, |
|||
CustomDateHeaderText customDateHeaderText = null, |
|||
DateTime? dateFormat = null, |
|||
string dateLocale = null, |
|||
bool? disableImageGallery = null, |
|||
bool? isAttachmentUploading = null, |
|||
bool? isLastPage = null, |
|||
ChatL10nEn l10n = null, |
|||
OnAttachmentPressed onAttachmentPressed = null, |
|||
OnEndReached onEndReached = null, |
|||
float? onEndReachedThreshold = null, |
|||
OnMessageLongPress onMessageLongPress = null, |
|||
OnMessageTap onMessageTap = null, |
|||
OnPreviewDataFetched onPreviewDataFetched = null, |
|||
OnTextChanged onTextChanged = null, |
|||
bool showUserAvatars = false, |
|||
bool showUserNames = false, |
|||
ChatTheme theme = null, |
|||
DateTime? timeFormat = null, |
|||
bool usePreviewData = true |
|||
) : base(key) |
|||
{ |
|||
this.buildCustomMessage = buildCustomMessage; |
|||
this.customDateHeaderText = customDateHeaderText; |
|||
this.dateFormat = dateFormat; |
|||
this.dateLocale = dateLocale; |
|||
this.disableImageGallery = disableImageGallery; |
|||
this.isAttachmentUploading = isAttachmentUploading; |
|||
this.isLastPage = isLastPage; |
|||
this.l10n = l10n == null ? new ChatL10nEn() : l10n; |
|||
this.messages = messages; |
|||
this.onAttachmentPressed = onAttachmentPressed; |
|||
this.onEndReached = onEndReached; |
|||
this.onEndReachedThreshold = onEndReachedThreshold; |
|||
this.onMessageLongPress = onMessageLongPress; |
|||
this.onMessageTap = onMessageTap; |
|||
this.onPreviewDataFetched = onPreviewDataFetched; |
|||
this.onSendPressed = onSendPressed; |
|||
this.onTextChanged = onTextChanged; |
|||
this.showUserAvatars = showUserAvatars; |
|||
this.showUserNames = showUserNames; |
|||
this.theme = theme == null ? new DefaultChatTheme() : theme; |
|||
this.timeFormat = timeFormat; |
|||
this.usePreviewData = usePreviewData; |
|||
this.user = user; |
|||
} |
|||
|
|||
public override State createState() |
|||
{ |
|||
return new _ChatState(); |
|||
} |
|||
} |
|||
|
|||
/// [Chat] widget state
|
|||
public class _ChatState : State<Chat> |
|||
{ |
|||
private List<object> _chatMessages = new List<object>(); |
|||
private List<PreviewImage> _gallery = new List<PreviewImage>(); |
|||
private int _imageViewIndex; |
|||
private bool _isImageViewVisible; |
|||
|
|||
public override void initState() |
|||
{ |
|||
base.initState(); |
|||
|
|||
didUpdateWidget(widget); |
|||
} |
|||
|
|||
public override void didUpdateWidget(StatefulWidget oldWidget) |
|||
{ |
|||
oldWidget = (Chat) oldWidget; |
|||
base.didUpdateWidget((Chat)oldWidget); |
|||
|
|||
if (widget.messages.isNotEmpty()) |
|||
{ |
|||
var result = ChatUtils.calculateChatMessages( |
|||
widget.messages, |
|||
widget.user, |
|||
customDateHeaderText: widget.customDateHeaderText, |
|||
dateFormat: widget.dateFormat, |
|||
dateLocale: widget.dateLocale, |
|||
showUserNames: widget.showUserNames, |
|||
timeFormat: widget.timeFormat |
|||
); |
|||
|
|||
_chatMessages = result[0] as List<object>; |
|||
_gallery = result[1] as List<PreviewImage>; |
|||
} |
|||
} |
|||
|
|||
/*private Widget _buildImageGallery() |
|||
{ |
|||
return new Dismissible( |
|||
GlobalKey.key("photo_view_gallery"), |
|||
direction: |
|||
DismissDirection.down, |
|||
onDismissed: |
|||
direction => _onCloseGalleryPressed(), |
|||
child: |
|||
new Stack( |
|||
children: new List<Widget> |
|||
{ |
|||
PhotoViewGallery.builder( |
|||
builder: (BuildContext context, int index) => |
|||
PhotoViewGalleryPageOptions( |
|||
imageProvider: new BrowserConditional().getProvider(_gallery[index].uri) |
|||
), |
|||
itemCount: _gallery.Count, |
|||
loadingBuilder: (_context, _event) => |
|||
_imageGalleryLoadingBuilder(_context, _event), |
|||
onPageChanged: |
|||
_onPageChanged, |
|||
pageController: |
|||
new PageController(_imageViewIndex), |
|||
scrollPhysics: |
|||
new ClampingScrollPhysics() |
|||
), |
|||
new Positioned( |
|||
right: 16, |
|||
top: 56, |
|||
child: new CloseButton( |
|||
color: Colors.white, |
|||
onPressed: _onCloseGalleryPressed |
|||
) |
|||
) |
|||
} |
|||
) |
|||
); |
|||
}*/ |
|||
|
|||
private Widget _buildMessage(object _object) |
|||
{ |
|||
if (_object is DateHeader) |
|||
{ |
|||
return new Container( |
|||
alignment: Alignment.center, |
|||
margin: EdgeInsets.only( |
|||
bottom: 32, |
|||
top: 16 |
|||
), |
|||
child: |
|||
new Text( |
|||
((DateHeader) _object).text, |
|||
style: widget.theme.dateDividerTextStyle |
|||
) |
|||
); |
|||
|
|||
} |
|||
|
|||
else if (_object is MessageSpacer) |
|||
{ |
|||
var height = ((MessageSpacer) _object).height; |
|||
return new SizedBox( |
|||
height: height |
|||
); |
|||
|
|||
} |
|||
else |
|||
{ |
|||
|
|||
var map = _object as Dictionary<string, object>; |
|||
var message = map["message"] as ChatComponents.Message; |
|||
var _messageWidth = |
|||
widget.showUserAvatars && message.author.id != widget.user.id |
|||
? Mathf.Min(MediaQuery.of(context).size.width * 0.72f, 440).floor() |
|||
: Mathf.Min(MediaQuery.of(context).size.width * 0.78f, 440).floor(); |
|||
|
|||
return new Message( |
|||
key: new ValueKey<string>(message.id), |
|||
buildCustomMessage: widget.buildCustomMessage, |
|||
message: message, |
|||
messageWidth: _messageWidth, |
|||
onMessageLongPress: widget.onMessageLongPress, |
|||
onMessageTap: tappedMessage => |
|||
{ |
|||
if (tappedMessage is ImageMessage && widget.disableImageGallery != true) |
|||
_onImagePressed((ImageMessage) tappedMessage); |
|||
|
|||
widget.onMessageTap?.Invoke(tappedMessage); |
|||
}, |
|||
onPreviewDataFetched: |
|||
(previewData,textMessage)=> |
|||
{ |
|||
_onPreviewDataFetched( textMessage,previewData); |
|||
}, |
|||
roundBorder: true, //(map["nextMessageInGroup"] is bool ? (bool) map["nextMessageInGroup"] : false),
|
|||
showAvatar: |
|||
widget.showUserAvatars && false, |
|||
//(map["nextMessageInGroup"] is bool ? (bool) map["nextMessageInGroup"] : false) == false,
|
|||
showName: |
|||
false, |
|||
//(map["showName"] is bool ? (bool) map["showName"] : false),
|
|||
showStatus: |
|||
false, |
|||
//(map["showStatus"] is bool ? (bool) map["showStatus"] : false),
|
|||
showUserAvatars: |
|||
widget.showUserAvatars, |
|||
usePreviewData: |
|||
widget.usePreviewData |
|||
); |
|||
} |
|||
|
|||
} |
|||
|
|||
private Widget _imageGalleryLoadingBuilder( |
|||
BuildContext context, |
|||
ImageChunkEvent _event = null |
|||
) |
|||
{ |
|||
return new Center( |
|||
child: new SizedBox( |
|||
width: 20.0f, |
|||
height: 20.0f, |
|||
child: new CircularProgressIndicator( |
|||
value: _event == null || _event.expectedTotalBytes == null |
|||
? 0 |
|||
: _event.cumulativeBytesLoaded / _event.expectedTotalBytes |
|||
) |
|||
) |
|||
); |
|||
} |
|||
|
|||
private void _onCloseGalleryPressed() |
|||
{ |
|||
setState(() => { _isImageViewVisible = false; }); |
|||
} |
|||
|
|||
private void _onImagePressed(ImageMessage message) |
|||
{ |
|||
setState(() => |
|||
{ |
|||
/*_imageViewIndex = _gallery.Where( |
|||
element => element.id == message.id && element.uri == message.uri |
|||
);*/ |
|||
foreach (var element in _gallery) |
|||
if (element.id == message.id && element.uri == message.uri) |
|||
{ |
|||
_imageViewIndex = _gallery.IndexOf(element); |
|||
break; |
|||
} |
|||
|
|||
_isImageViewVisible = true; |
|||
}); |
|||
} |
|||
|
|||
private void _onPageChanged(int index) |
|||
{ |
|||
setState(() => { _imageViewIndex = index; }); |
|||
} |
|||
|
|||
private void _onPreviewDataFetched( |
|||
ChatComponents.TextMessage message, |
|||
PreviewData previewData |
|||
) |
|||
{ |
|||
widget.onPreviewDataFetched?.Invoke(previewData,message); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
var results = new List<Widget>(); |
|||
results.Add(new Container( |
|||
color: widget.theme.backgroundColor, |
|||
child: new SafeArea( |
|||
bottom: false, |
|||
child: new Column( |
|||
children: new List<Widget> |
|||
{ |
|||
new Flexible( |
|||
child: widget.messages.isEmpty() |
|||
? (Widget) SizedBox.expand( |
|||
child: new Container( |
|||
//color: Colors.yellow,
|
|||
alignment: Alignment.center, |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: 24 |
|||
), |
|||
child: new Text( |
|||
widget.l10n.emptyChatPlaceholder, |
|||
style: widget.theme.emptyChatPlaceholderTextStyle, |
|||
textAlign: TextAlign.center |
|||
) |
|||
) |
|||
) |
|||
: new GestureDetector( |
|||
onTap: () => FocusManager.instance.primaryFocus?.unfocus(), |
|||
child: new ChatList( |
|||
isLastPage: widget.isLastPage, |
|||
itemBuilder: (item, index) => _buildMessage(item), |
|||
items: _chatMessages, |
|||
onEndReached: widget.onEndReached, |
|||
onEndReachedThreshold: widget.onEndReachedThreshold |
|||
) |
|||
) |
|||
), |
|||
new Input( |
|||
isAttachmentUploading: widget.isAttachmentUploading, |
|||
onAttachmentPressed: widget.onAttachmentPressed, |
|||
onSendPressed: widget.onSendPressed, |
|||
onTextChanged: widget.onTextChanged |
|||
) |
|||
} |
|||
) |
|||
) |
|||
)); |
|||
// if (_isImageViewVisible)
|
|||
// results.Add(_buildImageGallery());
|
|||
var test = widget.l10n; |
|||
|
|||
return new InheritedUser( |
|||
widget.user, |
|||
new InheritedChatTheme( |
|||
widget.theme, |
|||
new InheritedL10n( |
|||
widget.l10n, |
|||
new Stack( |
|||
children: results |
|||
) |
|||
) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public class InheritedL10n : InheritedWidget |
|||
{ |
|||
/// Represents localized copy
|
|||
public readonly ChatL10n l10n; |
|||
|
|||
/// Creates [InheritedWidget] from a provided [ChatL10n] class
|
|||
public InheritedL10n( |
|||
ChatL10n l10n, |
|||
Widget child, |
|||
Key key = null |
|||
) : base(key, child) |
|||
{ |
|||
this.l10n = l10n; |
|||
} |
|||
|
|||
public static InheritedL10n of(BuildContext context) |
|||
{ |
|||
return context.dependOnInheritedWidgetOfExactType<InheritedL10n>(); |
|||
} |
|||
|
|||
public override bool updateShouldNotify(InheritedWidget oldWidget) |
|||
|
|||
{ |
|||
var test = ((InheritedL10n) oldWidget).l10n; |
|||
return l10n.GetHashCode() != ((InheritedL10n) oldWidget).l10n?.GetHashCode(); |
|||
} |
|||
} |
|||
} |
|
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
internal class InheritedChatTheme : InheritedWidget |
|||
{ |
|||
/// Represents chat theme
|
|||
public readonly ChatTheme theme; |
|||
|
|||
/// Creates [InheritedWidget] from a provided [ChatTheme] class
|
|||
public InheritedChatTheme( |
|||
ChatTheme theme, |
|||
Widget child, |
|||
Key key = null |
|||
) : base(key, child) |
|||
{ |
|||
this.theme = theme; |
|||
} |
|||
|
|||
public static InheritedChatTheme of(BuildContext context) |
|||
{ |
|||
return context.dependOnInheritedWidgetOfExactType<InheritedChatTheme>(); |
|||
} |
|||
|
|||
|
|||
public override bool updateShouldNotify(InheritedWidget oldWidget) |
|||
{ |
|||
var result = theme.GetHashCode() != ((InheritedChatTheme) oldWidget).theme.GetHashCode(); |
|||
return result; |
|||
} |
|||
} |
|||
} |
|
|||
using ChatComponents; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
internal class InheritedUser : InheritedWidget |
|||
{ |
|||
/// Represents current logged in user. Used to determine message's author.
|
|||
public readonly User user; |
|||
|
|||
/// Creates [InheritedWidget] from a provided [types.User] class
|
|||
public InheritedUser( |
|||
User user, |
|||
Widget child, |
|||
Key key = null |
|||
) : base(key, child) |
|||
{ |
|||
this.user = user; |
|||
} |
|||
|
|||
public static InheritedUser of(BuildContext context) |
|||
{ |
|||
return context.dependOnInheritedWidgetOfExactType<InheritedUser>(); |
|||
} |
|||
|
|||
public override bool updateShouldNotify(InheritedWidget oldWidget) |
|||
{ |
|||
var result = user.id != ((InheritedUser) oldWidget).user.id; |
|||
return result; |
|||
} |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
using ChatComponents; |
|||
using uiwidgets; |
|||
using Unity.UIWidgets.animation; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.service; |
|||
using Unity.UIWidgets.services; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using System; |
|||
|
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public class NewLineIntent : Intent |
|||
{ |
|||
public NewLineIntent(LocalKey key ) : base(key) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
public class SendMessageIntent : Intent |
|||
{ |
|||
public SendMessageIntent(LocalKey key ) : base(key) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
/// A class that represents bottom bar widget with a text field, attachment and
|
|||
/// send buttons inside. Hides send button when text field is empty.
|
|||
public class Input : StatefulWidget |
|||
{ |
|||
/// Whether attachment is uploading. Will replace attachment button with a
|
|||
/// [CircularProgressIndicator]. Since we don't have libraries for
|
|||
/// managing media in dependencies we have no way of knowing if
|
|||
/// something is uploading so you need to set this manually.
|
|||
public readonly bool? isAttachmentUploading; |
|||
|
|||
/// See [AttachmentButton.onPressed]
|
|||
public readonly OnAttachmentPressed onAttachmentPressed; |
|||
|
|||
/// Will be called on [SendButton] tap. Has [types.PartialText] which can
|
|||
/// be transformed to [types.TextMessage] and added to the messages list.
|
|||
public readonly OnSendPressed onSendPressed; |
|||
|
|||
/// Will be called whenever the text inside [TextField] changes
|
|||
public readonly OnTextChanged onTextChanged; |
|||
|
|||
/// Creates [Input] widget
|
|||
public Input( |
|||
OnSendPressed onSendPressed, |
|||
Key key = null, |
|||
bool? isAttachmentUploading = null, |
|||
OnAttachmentPressed onAttachmentPressed = null, |
|||
OnTextChanged onTextChanged = null |
|||
) : base(key) |
|||
{ |
|||
this.onAttachmentPressed = onAttachmentPressed; |
|||
this.isAttachmentUploading = isAttachmentUploading; |
|||
this.onSendPressed = onSendPressed; |
|||
this.onTextChanged = onTextChanged; |
|||
} |
|||
|
|||
public override State createState() |
|||
{ |
|||
return new _InputState(); |
|||
} |
|||
} |
|||
|
|||
/// [Input] widget state
|
|||
internal class _InputState : State<Input> |
|||
{ |
|||
public readonly FocusNode _inputFocusNode = new FocusNode(); |
|||
public readonly TextEditingController _textController = new TextEditingController(); |
|||
private bool _sendButtonVisible; |
|||
|
|||
public override void initState() |
|||
{ |
|||
base.initState(); |
|||
_textController.addListener(_handleTextControllerChange); |
|||
} |
|||
|
|||
public override void dispose() |
|||
{ |
|||
_inputFocusNode.dispose(); |
|||
_textController.dispose(); |
|||
base.dispose(); |
|||
} |
|||
|
|||
private void _handleSendPressed() |
|||
{ |
|||
var _partialText = new PartialText(_textController.text.TrimEnd()); |
|||
widget.onSendPressed(_partialText); |
|||
_textController.clear(); |
|||
} |
|||
|
|||
private void _handleTextControllerChange() |
|||
{ |
|||
setState(() => |
|||
{ |
|||
_sendButtonVisible = _textController.text != ""; |
|||
}); |
|||
} |
|||
|
|||
private Widget _leftWidget() |
|||
{ |
|||
if (widget.isAttachmentUploading == true) |
|||
return new SizedBox( |
|||
height: 14, |
|||
width: 24, |
|||
child: new CircularProgressIndicator( |
|||
backgroundColor: Colors.transparent, |
|||
strokeWidth: 2, |
|||
valueColor: new AlwaysStoppedAnimation<Color>( |
|||
InheritedChatTheme.of(context).theme.inputTextColor |
|||
) |
|||
) |
|||
); |
|||
else |
|||
{ |
|||
return new AttachmentButton(onPressed: ()=> |
|||
{ |
|||
widget.onAttachmentPressed(); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public CallbackAction sendPress(){ |
|||
return new CallbackAction( |
|||
intentKey: DoNothingAction.key, |
|||
onInvoke: (FocusNode node, Intent intent) => _handleSendPressed() |
|||
); |
|||
} |
|||
|
|||
public CallbackAction sendSelectionPress() |
|||
{ |
|||
return new CallbackAction( |
|||
intentKey: DoNothingAction.key, |
|||
onInvoke: (FocusNode node, Intent intent) => |
|||
{ |
|||
var _newValue = $"{_textController.text}\r\n"; |
|||
_textController.value = new TextEditingValue( |
|||
_newValue, |
|||
TextSelection.fromPosition( |
|||
new TextPosition(_newValue.Length) |
|||
) |
|||
); |
|||
} |
|||
); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
var _query = MediaQuery.of(context); |
|||
var results = new List<Widget>(); |
|||
if (widget.onAttachmentPressed != null) |
|||
results.Add(_leftWidget()); |
|||
var hintStyle = InheritedChatTheme.of(context)? |
|||
.theme |
|||
.inputTextStyle |
|||
.copyWith( |
|||
color: InheritedChatTheme.of(context)? |
|||
.theme |
|||
.inputTextColor |
|||
.withOpacity(0.5f) |
|||
); |
|||
var hintText = InheritedL10n.of(context)?.l10n?.inputPlaceholder; |
|||
var decoration = InputDecoration.collapsed( |
|||
hintStyle: hintStyle, |
|||
hintText: hintText |
|||
); |
|||
var style = InheritedChatTheme.of(context) |
|||
.theme |
|||
.inputTextStyle |
|||
.copyWith( |
|||
color: InheritedChatTheme.of(context) |
|||
.theme |
|||
.inputTextColor |
|||
); |
|||
|
|||
var item = new Expanded( |
|||
child: new TextField( |
|||
controller: _textController, |
|||
decoration: decoration , |
|||
focusNode: _inputFocusNode, |
|||
keyboardType: TextInputType.multiline, |
|||
maxLines: 5, |
|||
minLines: 1, |
|||
onChanged: (_str) => |
|||
{ |
|||
widget.onTextChanged(_str); |
|||
}, |
|||
style: style, |
|||
textCapitalization: TextCapitalization.sentences |
|||
) |
|||
); |
|||
results.Add(item); |
|||
results.Add(new Visibility( |
|||
visible: _sendButtonVisible, |
|||
child: new SendButton( |
|||
onPressed: _handleSendPressed |
|||
) |
|||
)); |
|||
var shortCutsKey = new Dictionary<LogicalKeySet, Intent> |
|||
{ |
|||
{new LogicalKeySet(LogicalKeyboardKey.enter), new SendMessageIntent(DoNothingAction.key)}, |
|||
{ |
|||
new LogicalKeySet(LogicalKeyboardKey.enter, LogicalKeyboardKey.alt), |
|||
new NewLineIntent(DoNothingAction.key) |
|||
}, |
|||
{ |
|||
new LogicalKeySet(LogicalKeyboardKey.enter, LogicalKeyboardKey.shift), |
|||
new NewLineIntent(DoNothingAction.key) |
|||
} |
|||
}; |
|||
var actionKeys = new Dictionary<LocalKey, ActionFactory> |
|||
{ |
|||
{new ObjectKey(typeof(SendMessageIntent)), sendPress}, |
|||
{new ObjectKey(typeof(NewLineIntent)), sendSelectionPress} |
|||
}; |
|||
var test = _query; |
|||
return new GestureDetector( |
|||
onTap: () => _inputFocusNode.requestFocus(), |
|||
child: new Shortcuts( |
|||
shortcuts: shortCutsKey , |
|||
child: new Actions( |
|||
actions: actionKeys, |
|||
child: new Focus( |
|||
autofocus: true, |
|||
child: new Material( |
|||
borderRadius: InheritedChatTheme.of(context).theme.inputBorderRadius, |
|||
color: InheritedChatTheme.of(context).theme.inputBackgroundColor, |
|||
child: new Container( |
|||
|
|||
/*padding: EdgeInsets.fromLTRB( |
|||
left:24 + _query.padding.left, |
|||
top:0, |
|||
right:24 + _query.padding.right, |
|||
bottom:0//10 + _query.viewInsets.bottom + _query.padding.bottom
|
|||
),*/ |
|||
child: new Row( |
|||
children: results |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
using ChatComponents; |
|||
using uiwidgets; |
|||
using Unity.UIWidgets.animation; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using Image = Unity.UIWidgets.widgets.Image; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
internal class Message : StatelessWidget |
|||
{ |
|||
/// Build a custom message inside predefined bubble
|
|||
public readonly BuildCustomMessage buildCustomMessage; |
|||
|
|||
|
|||
/// Any message type
|
|||
public readonly ChatComponents.Message message; |
|||
|
|||
/// Maximum message width
|
|||
public readonly int messageWidth; |
|||
|
|||
/// Called when user makes a long press on any message
|
|||
public readonly OnMessageLongPress onMessageLongPress; |
|||
|
|||
/// Called when user taps on any message
|
|||
public readonly OnMessageTap onMessageTap; |
|||
|
|||
/// See [TextMessage.onPreviewDataFetched]
|
|||
public readonly OnPreviewDataFetched onPreviewDataFetched; |
|||
|
|||
/// Rounds border of the message to visually group messages together.
|
|||
public readonly bool roundBorder; |
|||
|
|||
/// Show user avatar for the received message. Useful for a group chat.
|
|||
public readonly bool showAvatar; |
|||
|
|||
/// See [TextMessage.showName]
|
|||
public readonly bool showName; |
|||
|
|||
/// Show message's status
|
|||
public readonly bool showStatus; |
|||
|
|||
/// Show user avatars for received messages. Useful for a group chat.
|
|||
public readonly bool showUserAvatars; |
|||
|
|||
/// See [TextMessage.usePreviewData]
|
|||
public readonly bool usePreviewData; |
|||
|
|||
/// Creates a particular message from any message type
|
|||
public Message( |
|||
ChatComponents.Message message, |
|||
int messageWidth, |
|||
bool roundBorder, |
|||
bool showAvatar, |
|||
bool showName, |
|||
bool showStatus, |
|||
bool showUserAvatars, |
|||
bool usePreviewData, |
|||
Key key = null, |
|||
OnMessageLongPress onMessageLongPress = null, |
|||
OnMessageTap onMessageTap = null, |
|||
OnPreviewDataFetched onPreviewDataFetched = null, |
|||
BuildCustomMessage buildCustomMessage = null |
|||
) : base(key) |
|||
{ |
|||
this.message = message; |
|||
this.messageWidth = messageWidth; |
|||
this.buildCustomMessage = buildCustomMessage; |
|||
this.onMessageTap = onMessageTap; |
|||
this.onMessageLongPress = onMessageLongPress; |
|||
this.onPreviewDataFetched = onPreviewDataFetched; |
|||
this.roundBorder = roundBorder; |
|||
this.showAvatar = showAvatar; |
|||
this.showName = showName; |
|||
this.showStatus = showStatus; |
|||
this.showUserAvatars = showUserAvatars; |
|||
this.usePreviewData = usePreviewData; |
|||
} |
|||
|
|||
private Widget _buildAvatar(BuildContext context) |
|||
{ |
|||
var color = ChatUtils.getUserAvatarNameColor(message.author, |
|||
InheritedChatTheme.of(context).theme.userAvatarNameColors); |
|||
var hasImage = message.author.imageUrl != null; |
|||
string name = ChatUtils.getUserName(message.author); |
|||
|
|||
return showAvatar |
|||
? new Container( |
|||
margin: EdgeInsets.only(right: 8), |
|||
child: new CircleAvatar( |
|||
backgroundImage: |
|||
hasImage ? new NetworkImage(message.author.imageUrl == null ? "" : (string)message.author.imageUrl) : null, |
|||
backgroundColor: color, |
|||
radius: 16, |
|||
child: !hasImage |
|||
? new Text( |
|||
name.isEmpty() ? "" : name[0].ToString().ToUpper(), |
|||
style: InheritedChatTheme.of(context).theme.userAvatarTextStyle |
|||
) |
|||
: null |
|||
) |
|||
) |
|||
: new Container( |
|||
margin: EdgeInsets.only(right: 40) |
|||
); |
|||
} |
|||
|
|||
private Widget _buildMessage() |
|||
{ |
|||
switch (message.type) |
|||
{ |
|||
case MessageType.custom: |
|||
var customMessage = message as CustomMessage; |
|||
return buildCustomMessage != null |
|||
? buildCustomMessage(customMessage) |
|||
: new SizedBox(); |
|||
/*case MessageType.file: |
|||
var fileMessage = message as ChatComponents.FileMessage; |
|||
return new FileMessage( |
|||
message: fileMessage |
|||
); |
|||
case MessageType.image: |
|||
var imageMessage = message as ChatComponents.ImageMessage; |
|||
return new ImageMessage( |
|||
message: imageMessage, |
|||
messageWidth: messageWidth |
|||
);*/ |
|||
case MessageType.text: |
|||
var textMessage = message as ChatComponents.TextMessage; |
|||
return new TextMessage( |
|||
message: textMessage, |
|||
onPreviewDataFetched: onPreviewDataFetched, |
|||
showName: showName, |
|||
usePreviewData: usePreviewData |
|||
); |
|||
default: |
|||
return new SizedBox(); |
|||
} |
|||
} |
|||
|
|||
private Widget _buildStatus(BuildContext context) |
|||
{ |
|||
switch (message.status) |
|||
{ |
|||
case Status.error: |
|||
return InheritedChatTheme.of(context).theme.errorIcon != null |
|||
? InheritedChatTheme.of(context).theme.errorIcon |
|||
: Image.asset( |
|||
"assets/icon-error.png", |
|||
color: InheritedChatTheme.of(context).theme.errorColor, |
|||
package: "flutter_chat_ui" |
|||
); |
|||
case Status.sent: |
|||
case Status.delivered: |
|||
return InheritedChatTheme.of(context).theme.deliveredIcon != null |
|||
? InheritedChatTheme.of(context).theme.deliveredIcon |
|||
: Image.asset( |
|||
"assets/icon-delivered.png", |
|||
color: InheritedChatTheme.of(context).theme.primaryColor, |
|||
package: "flutter_chat_ui" |
|||
); |
|||
case Status.seen: |
|||
return InheritedChatTheme.of(context).theme.seenIcon != null |
|||
? InheritedChatTheme.of(context).theme.seenIcon |
|||
: Image.file( |
|||
"assets/icon-seen.png", |
|||
color: InheritedChatTheme.of(context).theme.primaryColor, |
|||
scale:1 |
|||
//package: "flutter_chat_ui"
|
|||
); |
|||
|
|||
case Status.sending: |
|||
return new Center( |
|||
child: new SizedBox( |
|||
height: 10, |
|||
width: 10, |
|||
child: new CircularProgressIndicator( |
|||
backgroundColor: Colors.transparent, |
|||
strokeWidth: 1.5f, |
|||
valueColor: new AlwaysStoppedAnimation<Color>( |
|||
InheritedChatTheme.of(context).theme.primaryColor |
|||
) |
|||
) |
|||
) |
|||
); |
|||
default: |
|||
return new SizedBox(); |
|||
} |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
var _user = InheritedUser.of(context).user; |
|||
var _messageBorderRadius = |
|||
InheritedChatTheme.of(context).theme.messageBorderRadius; |
|||
var _borderRadius = BorderRadius.only( |
|||
bottomLeft: Radius.circular(_user.id == message.author.id || roundBorder |
|||
? _messageBorderRadius |
|||
: 0), |
|||
bottomRight: Radius.circular(_user.id == message.author.id |
|||
? roundBorder |
|||
? _messageBorderRadius |
|||
: 0 |
|||
: _messageBorderRadius), |
|||
topLeft: Radius.circular(_messageBorderRadius), |
|||
topRight: Radius.circular(_messageBorderRadius) |
|||
); |
|||
var _currentUserIsAuthor = _user.id == message.author.id; |
|||
var results = new List<Widget>(); |
|||
if (!_currentUserIsAuthor && showUserAvatars) |
|||
results.Add(_buildAvatar(context)); |
|||
results.Add(new ConstrainedBox( |
|||
constraints: new BoxConstraints( |
|||
maxWidth: messageWidth * 1f |
|||
), |
|||
child: new Column( |
|||
crossAxisAlignment: CrossAxisAlignment.end, |
|||
children: new List<Widget> |
|||
{ |
|||
new GestureDetector( |
|||
onLongPress: () => onMessageLongPress?.Invoke(message), |
|||
onTap: () => onMessageTap?.Invoke(message), |
|||
child: new Container( |
|||
decoration: new BoxDecoration( |
|||
borderRadius: _borderRadius, |
|||
color: !_currentUserIsAuthor || |
|||
message.type == MessageType.image |
|||
? InheritedChatTheme.of(context).theme.secondaryColor |
|||
: InheritedChatTheme.of(context).theme.primaryColor |
|||
), |
|||
child: new ClipRRect( |
|||
borderRadius: _borderRadius, |
|||
child: _buildMessage() |
|||
) |
|||
) |
|||
) |
|||
} |
|||
) |
|||
)); |
|||
if (_currentUserIsAuthor) |
|||
results.Add(new Padding( |
|||
padding: EdgeInsets.symmetric(horizontal: 4), |
|||
child: new Center( |
|||
child: new SizedBox( |
|||
height: 16, |
|||
width: 16, |
|||
child: showStatus ? _buildStatus(context) : null |
|||
) |
|||
) |
|||
)); |
|||
return new Container( |
|||
alignment: _user.id == message.author.id |
|||
? Alignment.centerRight |
|||
: Alignment.centerLeft, |
|||
margin: EdgeInsets.only( |
|||
bottom: 4, |
|||
left: 20 |
|||
), |
|||
|
|||
child: new Row( |
|||
crossAxisAlignment: CrossAxisAlignment.end, |
|||
mainAxisSize: MainAxisSize.min, |
|||
children: results) |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public class SendButton : StatelessWidget |
|||
{ |
|||
/// Callback for send button tap event
|
|||
public readonly OnPressed onPressed; |
|||
|
|||
/// Creates send button widget
|
|||
public SendButton( |
|||
OnPressed onPressed, |
|||
Key key = null |
|||
) : base(key) |
|||
{ |
|||
this.onPressed = onPressed; |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
Widget icon = InheritedChatTheme.of(context).theme.sendButtonIcon != null |
|||
? InheritedChatTheme.of(context).theme.sendButtonIcon |
|||
: Image.file( |
|||
"assets/icon-send.png", |
|||
color: InheritedChatTheme.of(context).theme.inputTextColor, |
|||
scale: 1 |
|||
); |
|||
var tooltip = InheritedL10n.of(context).l10n?.sendButtonAccessibilityLabel; |
|||
return new Container( |
|||
height: 14, |
|||
//margin: EdgeInsets.only(16),
|
|||
width: 24, |
|||
child: new IconButton( |
|||
icon: icon, |
|||
onPressed: () => { onPressed(); }, |
|||
padding: EdgeInsets.zero, |
|||
tooltip: tooltip |
|||
) |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
using System.Text.RegularExpressions; |
|||
using ChatComponents; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
internal class TextMessage : StatelessWidget |
|||
{ |
|||
/// [types.TextMessage]
|
|||
public readonly ChatComponents.TextMessage message; |
|||
|
|||
/// See [LinkPreview.onPreviewDataFetched]
|
|||
public readonly OnPreviewDataFetched |
|||
onPreviewDataFetched; |
|||
|
|||
/// Show user name for the received message. Useful for a group chat.
|
|||
public readonly bool showName; |
|||
|
|||
/// Enables link (URL) preview
|
|||
public readonly bool usePreviewData; |
|||
|
|||
/// Creates a text message widget from a [types.TextMessage] class
|
|||
public TextMessage( |
|||
ChatComponents.TextMessage message, |
|||
bool usePreviewData, |
|||
bool showName, |
|||
Key key = null, |
|||
OnPreviewDataFetched onPreviewDataFetched = null |
|||
) : base(key) |
|||
{ |
|||
this.message = message; |
|||
this.usePreviewData = usePreviewData; |
|||
this.showName = showName; |
|||
this.onPreviewDataFetched = onPreviewDataFetched; |
|||
} |
|||
|
|||
private void _onPreviewDataFetched(PreviewData previewData) |
|||
{ |
|||
if (message.previewData == null) |
|||
onPreviewDataFetched?.Invoke(previewData,message); |
|||
} |
|||
|
|||
private Widget _linkPreview( |
|||
User user, |
|||
float width, |
|||
BuildContext context |
|||
) |
|||
{ |
|||
var bodyTextStyle = user.id == message.author.id |
|||
? InheritedChatTheme.of(context).theme.sentMessageBodyTextStyle |
|||
: InheritedChatTheme.of(context).theme.receivedMessageBodyTextStyle; |
|||
var linkDescriptionTextStyle = user.id == message.author.id |
|||
? InheritedChatTheme.of(context) |
|||
.theme |
|||
.sentMessageLinkDescriptionTextStyle |
|||
: InheritedChatTheme.of(context) |
|||
.theme |
|||
.receivedMessageLinkDescriptionTextStyle; |
|||
var linkTitleTextStyle = user.id == message.author.id |
|||
? InheritedChatTheme.of(context).theme.sentMessageLinkTitleTextStyle |
|||
: InheritedChatTheme.of(context) |
|||
.theme |
|||
.receivedMessageLinkTitleTextStyle; |
|||
var test = InheritedChatTheme.of(context); |
|||
var color = ChatUtils.getUserAvatarNameColor(message.author, InheritedChatTheme.of(context).theme.userAvatarNameColors); |
|||
var name = ChatUtils.getUserName(message.author); |
|||
|
|||
return new LinkPreview( |
|||
width: width, |
|||
enableAnimation: true, |
|||
header: showName ? name : null, |
|||
headerStyle: InheritedChatTheme.of(context) |
|||
.theme |
|||
.userNameTextStyle |
|||
.copyWith(color: color), |
|||
linkStyle: bodyTextStyle, |
|||
metadataTextStyle: linkDescriptionTextStyle, |
|||
metadataTitleStyle: linkTitleTextStyle, |
|||
onPreviewDataFetched: (previewData,textMessage)=> |
|||
{ |
|||
_onPreviewDataFetched(previewData); |
|||
}, |
|||
padding: EdgeInsets.symmetric( |
|||
horizontal: 24, |
|||
vertical: 16 |
|||
), |
|||
previewData: message.previewData, |
|||
text: message.text, |
|||
textStyle: bodyTextStyle |
|||
|
|||
); |
|||
} |
|||
|
|||
private Widget _textWidget(User user, BuildContext context) |
|||
{ |
|||
var color = ChatUtils.getUserAvatarNameColor(message.author, |
|||
InheritedChatTheme.of(context).theme.userAvatarNameColors); |
|||
var name = ChatUtils.getUserName(message.author); |
|||
var results = new List<Widget>(); |
|||
if (showName) |
|||
results.Add(new Padding( |
|||
padding: EdgeInsets.only(bottom: 6.0f), |
|||
child: new Text( |
|||
name, |
|||
maxLines: 1, |
|||
overflow: TextOverflow.ellipsis, |
|||
style: InheritedChatTheme.of(context) |
|||
.theme |
|||
.userNameTextStyle |
|||
.copyWith(color: color) |
|||
) |
|||
)); |
|||
var test = InheritedChatTheme.of(context); |
|||
var style = user.id == message.author.id |
|||
? InheritedChatTheme.of(context).theme.sentMessageBodyTextStyle |
|||
: InheritedChatTheme.of(context) |
|||
.theme |
|||
.receivedMessageBodyTextStyle; |
|||
results.Add(//new SelectableText(
|
|||
new Text( |
|||
data: message.text, |
|||
style: style , |
|||
textWidthBasis: TextWidthBasis.longestLine |
|||
)); |
|||
return new Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: results |
|||
); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
var _user = InheritedUser.of(context).user; |
|||
var _width = MediaQuery.of(context).size.width; |
|||
|
|||
var urlRegexp = new Regex(ChatUtils.REGEX_LINK); |
|||
var matches = urlRegexp.Match(message.text.ToLower()); |
|||
|
|||
if (matches.Length !=0 && usePreviewData && onPreviewDataFetched != null) |
|||
return _linkPreview(_user, _width, context); |
|||
|
|||
return new Container( |
|||
margin: EdgeInsets.symmetric( |
|||
horizontal: 24, |
|||
vertical: 16 |
|||
), |
|||
child: _textWidget(_user, context) |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using uiwidgets; |
|||
using Unity.UIWidgets.animation; |
|||
using Unity.UIWidgets.async; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.scheduler; |
|||
using Unity.UIWidgets.widgets; |
|||
using UnityEngine; |
|||
using Color = Unity.UIWidgets.ui.Color; |
|||
|
|||
namespace UIWidgetsSample |
|||
{ |
|||
public delegate Widget ItemBuilder(object _object, int? index); |
|||
|
|||
public class ChatList : StatefulWidget |
|||
{ |
|||
/// Used for pagination (infinite scroll) together with [onEndReached].
|
|||
/// When true, indicates that there are no more pages to load and
|
|||
/// pagination will not be triggered.
|
|||
public readonly bool? isLastPage; |
|||
|
|||
/// Item builder
|
|||
public readonly ItemBuilder itemBuilder; |
|||
|
|||
/// Items to build
|
|||
public readonly List<object> items; |
|||
|
|||
|
|||
/// Used for pagination (infinite scroll). Called when user scrolls
|
|||
/// to the very end of the list (minus [onEndReachedThreshold]).
|
|||
public readonly OnEndReached onEndReached; |
|||
|
|||
|
|||
/// Used for pagination (infinite scroll) together with [onEndReached].
|
|||
/// Can be anything from 0 to 1, where 0 is immediate load of the next page
|
|||
/// as soon as scroll starts, and 1 is load of the next page only if scrolled
|
|||
/// to the very end of the list. Default value is 0.75, e.g. start loading
|
|||
/// next page when scrolled through about 3/4 of the available content.
|
|||
public readonly float? onEndReachedThreshold; |
|||
|
|||
/// Creates a chat list widget
|
|||
public ChatList( |
|||
ItemBuilder itemBuilder, |
|||
List<object> items, |
|||
OnEndReached onEndReached = null, |
|||
float? onEndReachedThreshold = null, |
|||
Key key = null, |
|||
bool? isLastPage = null |
|||
) : base(key) |
|||
{ |
|||
this.itemBuilder = itemBuilder; |
|||
this.items = items; |
|||
this.onEndReached = onEndReached; |
|||
this.onEndReachedThreshold = onEndReachedThreshold; |
|||
this.isLastPage = isLastPage; |
|||
} |
|||
|
|||
public override State createState() |
|||
{ |
|||
return new _ChatListState(); |
|||
} |
|||
} |
|||
|
|||
/// [ChatList] widget state
|
|||
public class _ChatListState : SingleTickerProviderStateMixin<ChatList> |
|||
{ |
|||
public readonly GlobalKey<SliverAnimatedListState> _listKey = GlobalKey<SliverAnimatedListState>.key(); |
|||
public readonly ScrollController _scrollController = new ScrollController(); |
|||
|
|||
public Animation<float> _animation; |
|||
public AnimationController _controller; |
|||
public bool _isNextPageLoading; |
|||
public List<object> _oldData; |
|||
public List<string> _oldIndex; |
|||
|
|||
public override void initState() |
|||
{ |
|||
base.initState(); |
|||
|
|||
didUpdateWidget(widget); |
|||
_controller = new AnimationController(vsync: this); |
|||
_animation = new CurvedAnimation( |
|||
curve: Curves.easeOutQuad, |
|||
parent: _controller |
|||
); |
|||
_isNextPageLoading = false; |
|||
_oldData = new List<object>(); |
|||
_oldData.AddRange(widget.items); |
|||
_oldIndex = new List<string>(); |
|||
|
|||
} |
|||
|
|||
public override void didUpdateWidget(StatefulWidget oldWidget) |
|||
{ |
|||
oldWidget = (ChatList) oldWidget; |
|||
base.didUpdateWidget(oldWidget); |
|||
_calculateDiffs(((ChatList) oldWidget).items); |
|||
} |
|||
|
|||
public override void dispose() |
|||
{ |
|||
base.dispose(); |
|||
|
|||
_controller.dispose(); |
|||
_scrollController.dispose(); |
|||
} |
|||
|
|||
private void _calculateDiffs(List<object> oldList) |
|||
{ |
|||
|
|||
foreach (var item in widget.items) |
|||
{ |
|||
if (item is Dictionary<string, object> ) |
|||
{ |
|||
var message1 = ((Dictionary<string, object>)item)["message"] as ChatComponents.Message; |
|||
int test = widget.items.IndexOf(item); |
|||
if (message1 != null) |
|||
{ |
|||
if (_oldIndex != null && !_oldIndex.Contains(message1.id)) |
|||
{ |
|||
_listKey.currentState?.insertItem(0); |
|||
} |
|||
|
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
_scrollToBottomIfNeeded(oldList); |
|||
_oldData = new List<object>(widget.items); |
|||
List<string> _newIndex = new List<string>(); |
|||
foreach (var item in widget.items) |
|||
{ |
|||
if (item is Dictionary<string, object> ) |
|||
{ |
|||
var message1 = ((Dictionary<string, object>)item)["message"] as ChatComponents.Message; |
|||
if (message1 != null) |
|||
_newIndex.Add(message1.id); |
|||
} |
|||
} |
|||
|
|||
_oldIndex = _newIndex; |
|||
|
|||
} |
|||
|
|||
|
|||
// Hacky solution to reconsider
|
|||
private void _scrollToBottomIfNeeded(List<object> oldList) |
|||
{ |
|||
try |
|||
{ |
|||
// Take index 1 because there is always a spacer on index 0
|
|||
var oldItem = oldList[1]; |
|||
var item = widget.items[1]; |
|||
|
|||
// Compare items to fire only on newly added messages
|
|||
if (oldItem != item && item is Dictionary<string, object>) |
|||
{ |
|||
var message = ((Dictionary<string, object>) item)["message"] as ChatComponents.Message; |
|||
|
|||
// Run only for sent message
|
|||
if (message.author.id == |
|||
InheritedUser.of(context).user.id) // Delay to give some time for Flutter to calculate new
|
|||
// size after new message was added
|
|||
Future.delayed(TimeSpan.FromMilliseconds(100), () => |
|||
{ |
|||
_scrollController.animateTo( |
|||
0, |
|||
TimeSpan.FromMilliseconds(200), |
|||
Curves.easeInQuad |
|||
); |
|||
return default; |
|||
}); |
|||
} |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
// Do nothing if there are no items
|
|||
} |
|||
} |
|||
|
|||
private Widget _buildRemovedMessage(object item, Animation<float> animation) |
|||
{ |
|||
return new SizeTransition( |
|||
axisAlignment: -1, |
|||
sizeFactor: animation.drive(new CurveTween(Curves.easeInQuad)), |
|||
child: new FadeTransition( |
|||
opacity: animation.drive(new CurveTween(Curves.easeInQuad)), |
|||
child: widget.itemBuilder(item, null) |
|||
) |
|||
); |
|||
} |
|||
|
|||
private Widget _buildNewMessage(int index, Animation<float> animation) |
|||
{ |
|||
try |
|||
{ |
|||
var item = _oldData[index]; |
|||
|
|||
return new SizeTransition( |
|||
axisAlignment: -1, |
|||
sizeFactor: animation.drive(new CurveTween(Curves.easeOutQuad)), |
|||
child: widget.itemBuilder(item, index) |
|||
); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return new SizedBox(); |
|||
} |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
return new NotificationListener<ScrollNotification>( |
|||
onNotification: notification => |
|||
{ |
|||
if (widget.onEndReached == null || widget.isLastPage == true) return false; |
|||
|
|||
var _onEndReachedThreshold = |
|||
widget.onEndReachedThreshold == null ? 0.75f : (float) widget.onEndReachedThreshold; |
|||
if (notification.metrics.pixels >= notification.metrics.maxScrollExtent * _onEndReachedThreshold) |
|||
{ |
|||
if (widget.items.isEmpty() || _isNextPageLoading) |
|||
return false; |
|||
|
|||
SchedulerBinding.instance.addPostFrameCallback(stamp => |
|||
{ |
|||
_controller.duration = TimeSpan.Zero; |
|||
_controller.forward(); |
|||
|
|||
setState(() => { _isNextPageLoading = true; }); |
|||
|
|||
widget.onEndReached().whenComplete(() => |
|||
{ |
|||
_controller.duration = TimeSpan.FromMilliseconds(300); |
|||
_controller.reverse(); |
|||
setState(() => { _isNextPageLoading = false; }); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
return false; |
|||
}, |
|||
child: |
|||
new CustomScrollView( |
|||
controller: _scrollController, |
|||
reverse: true, |
|||
slivers: new List<Widget> |
|||
{ |
|||
new SliverPadding( |
|||
padding: EdgeInsets.only(bottom: 4), |
|||
sliver: new SliverAnimatedList( |
|||
initialItemCount: widget.items.Count, |
|||
key: _listKey, |
|||
itemBuilder: (_, index, animation) => |
|||
{ |
|||
return _buildNewMessage(index, animation); |
|||
} |
|||
) |
|||
), |
|||
new SliverPadding( |
|||
padding: EdgeInsets.only( |
|||
top: 16 |
|||
), |
|||
sliver: new SliverToBoxAdapter( |
|||
child: new SizeTransition( |
|||
axisAlignment: 1, |
|||
sizeFactor: _animation, |
|||
child: new Center( |
|||
child: new Container( |
|||
alignment: Alignment.center, |
|||
height: 32, |
|||
width: 32, |
|||
child: new SizedBox( |
|||
height: 16, |
|||
width: 16, |
|||
child: new CircularProgressIndicator( |
|||
backgroundColor: Colors.transparent, |
|||
strokeWidth: 2, |
|||
valueColor: new AlwaysStoppedAnimation<Color>( |
|||
InheritedChatTheme.of(context).theme.primaryColor |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
} |
|||
) |
|||
); |
|||
} |
|||
} |
|||
} |
撰写
预览
正在加载...
取消
保存
Reference in new issue