shiyun wen
3 年前
当前提交
69af7caf
共有 53 个文件被更改,包括 6190 次插入 和 8 次删除
-
3AwesomeUIWidgets/Assets/Scripts/CountDemo.cs
-
14AwesomeUIWidgets/Assets/Scripts/Utils/FlyCamera.cs
-
675AwesomeUIWidgets/Assets/Scenes/ChatRoomScene.unity
-
309AwesomeUIWidgets/Assets/Scripts/ChatPage.cs
-
64AwesomeUIWidgets/Assets/Scripts/chat_l10n.cs
-
487AwesomeUIWidgets/Assets/Scripts/chat_theme.cs
-
228AwesomeUIWidgets/Assets/Scripts/utils.cs
-
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
-
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
-
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
-
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
-
309AwesomeUIWidgets/Assets/Scripts/Widgets/chat_list.cs
-
33AwesomeUIWidgets/Assets/Scripts/Widgets/inherited_l10n.cs
-
33AwesomeUIWidgets/Assets/Scripts/Widgets/inherited_theme.cs
-
33AwesomeUIWidgets/Assets/Scripts/Widgets/inherited_user.cs
-
252AwesomeUIWidgets/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
-
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: 113} |
|||
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: 277.64478, 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: 0} |
|||
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: 1928952342} |
|||
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 &1928952342 |
|||
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.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 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-474ef323aaaa", |
|||
text: message.text |
|||
); |
|||
|
|||
_addMessage(textMessage); |
|||
} |
|||
|
|||
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, |
|||
user: _user |
|||
) |
|||
|
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
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; |
|||
} |
|||
} |
|||
} |
|
|||
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; |
|||
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; |
|||
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; |
|||
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 System; |
|||
using System.Collections.Generic; |
|||
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.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
|
|||
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 AnimationController _controller ; |
|||
|
|||
public Animation<float> _animation; |
|||
|
|||
public readonly GlobalKey<SliverAnimatedListState> _listKey = GlobalKey<SliverAnimatedListState>.key(); |
|||
public readonly ScrollController _scrollController = new ScrollController(); |
|||
private bool _isNextPageLoading; |
|||
public List<object> _oldData ; |
|||
|
|||
public override void initState() |
|||
{ |
|||
base.initState(); |
|||
|
|||
didUpdateWidget(widget); |
|||
_controller = new AnimationController(vsync: this); |
|||
_animation = new CurvedAnimation( |
|||
curve: Curves.easeOutQuad, |
|||
parent: _controller |
|||
); |
|||
_oldData = new List<object>(); |
|||
_oldData.AddRange(widget.items); |
|||
} |
|||
|
|||
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) |
|||
{ |
|||
_oldData = new List<object>(widget.items); |
|||
/*var diffResult = calculateListDiff<object>( |
|||
oldList, |
|||
widget.items, |
|||
equalityChecker: (item1, item2) => |
|||
{ |
|||
if (item1 is Dictionary<string, object> && item2 is Dictionary<string, object>) |
|||
{ |
|||
var message1 = item1["message"]! as ChatComponents.Message; |
|||
var message2 = item2["message"]! as ChatComponents.Message; |
|||
|
|||
return message1.id == message2.id; |
|||
} |
|||
|
|||
return item1 == item2; |
|||
} |
|||
); |
|||
foreach (var update in diffResult.getUpdates(batch: false)) |
|||
update.when( |
|||
insert: (pos, count) => { _listKey.currentState?.insertItem(pos); }, |
|||
remove: (pos, count) => |
|||
{ |
|||
var item = oldList[pos]; |
|||
_listKey.currentState?.removeItem( |
|||
pos, |
|||
(_, animation) => _buildRemovedMessage(item, animation) |
|||
); |
|||
}, |
|||
change: (pos, payload) => { }, |
|||
move: (from, to) => { } |
|||
); |
|||
|
|||
_scrollToBottomIfNeeded(oldList); |
|||
|
|||
_oldData = new List<object>(widget.items); |
|||
|
|||
foreach (var item1 in oldList) |
|||
{ |
|||
foreach (var item2 in widget.items) |
|||
{ |
|||
if (item1 is Dictionary<string, object> && item2 is Dictionary<string, object>) |
|||
{ |
|||
var message1 = ((Dictionary<string, object>)item1)["message"]! as ChatComponents.Message; |
|||
var message2 = ((Dictionary<string, object>)item2)["message"]! as ChatComponents.Message; |
|||
|
|||
return message1.id == message2.id; |
|||
} |
|||
|
|||
return item1 == item2; |
|||
} |
|||
} |
|||
|
|||
}*/ |
|||
|
|||
|
|||
} |
|||
|
|||
// 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; |
|||
|
|||
if (notification.metrics.pixels >= |
|||
notification.metrics.maxScrollExtent * |
|||
(widget.onEndReachedThreshold ?? 0.75)) |
|||
{ |
|||
if (widget.items.isEmpty() || _isNextPageLoading) return false; |
|||
|
|||
_controller.duration = new TimeSpan(); |
|||
_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) => |
|||
_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 |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
} |
|||
) |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
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 = null) : base(key) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
public class SendMessageIntent : Intent |
|||
{ |
|||
public SendMessageIntent(LocalKey key = null) : 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( |
|||
onInvoke: (FocusNode node, Intent intent) => _handleSendPressed() |
|||
); |
|||
} |
|||
|
|||
public CallbackAction sendSelectionPress() |
|||
{ |
|||
return new CallbackAction( |
|||
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()}, |
|||
{ |
|||
new LogicalKeySet(LogicalKeyboardKey.enter, LogicalKeyboardKey.alt), |
|||
new NewLineIntent() |
|||
}, |
|||
{ |
|||
new LogicalKeySet(LogicalKeyboardKey.enter, LogicalKeyboardKey.shift), |
|||
new NewLineIntent() |
|||
} |
|||
}; |
|||
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) |
|||
); |
|||
} |
|||
} |
|||
} |
撰写
预览
正在加载...
取消
保存
Reference in new issue