tim_ui_kit_lbs_plugin 1.0.8 tim_ui_kit_lbs_plugin: ^1.0.8 copied to clipboard
LBS plug-in with UIKit for Tencent IM Flutter, including the location picker, LBS messages show, etc.
Tencent Cloud IM Flutter UIKit LBS Message Plugin #
[English version] 中文版文档见后半部分
With this plugin, developers can implement lbs (location message) to projects easily.
We provided three widgets, including: location picker(LocationPicker)/ location shows in whole page(LocationShow)/ location shows in history message list(LocationMsgElement), with the related business logic referring to them, except those interact with Map SDK.
Also, we provided complicated example code based on Baidu Map, for the sake of easy integration, which could run directly, so developers can create the whole lbs project based on our code.
Vocabulary #
POI: Point of interesting, each point without geographical significance in the map. Each POI contains coordinate, name, address and ID. For example:The White House, The University of Sydney, etc.
Developing Introduction #
Overall Process: #
- Choose a specific Map SDK for Flutter
- Extends three abstract classes, in order to connect this plug-in to your Map SDK
- Instantiate the inherited abstract class and pass it into the three components provided by this plug-in
Choose a specific Map SDK for Flutter #
The choosing of Map SDK is not limited, here shows some links.
Baidu Map (in Chinese):https://lbsyun.baidu.com/index.php?title=flutter/loc
Google Map:https://codelabs.developers.google.com/codelabs/google-maps-in-flutter#0
Data Structure for interaction #
This plug-in defined several data structure models to describe the data transferred, especially between the abstract class been implemented with Map SDK and the lbs business function. Including:
class LocationMessage {
final String desc;
final double longitude;
final double latitude;
}
/// Coordinate
class TIMCoordinate implements TIMLocationBaseModel {
/// Latitude
late double latitude;
/// Longitude
late double longitude;
Map<String, Object> toMap();
fromMap(Map map);
}
/// POI information class
class TIMPoiInfo implements TIMLocationBaseModel {
/// POI name
String? name;
/// POI coordinate
TIMCoordinate? pt;
/// POI full address
String? address;
/// POI unique identifier 'uid'
String? uid;
/// Province where POI located
String? province;
/// City where POI located
String? city;
fromMap(Map map);
Map<String, Object?> toMap();
}
/// The result class of the reverse query according to the coordinate
class TIMReverseGeoCodeSearchResult implements TIMLocationBaseModel {
/// Coordinate
TIMCoordinate? location;
/// Full address
String? address;
/// Hierarchical address information
TIMAddressComponent? addressDetail;
/// POI information list around the searched address. Member type is TIMPoiInfo.
List<TIMPoiInfo>? poiList;
/// Semantic result description of current POI location, used as the address name.
/// Such as "100m south of the The Quarter Library, inside the Camperdown campus of the University of Sydney".
String? semanticDescription;
fromMap(Map map);
Map<String, Object?> toMap();
}
/// Hierarchical address information
class TIMAddressComponent implements TIMLocationBaseModel {
/// Country of the address
String? country;
/// Province of the address
String? province;
/// City of the address
String? city;
/// District of the address
String? district;
/// Town of the address
String? town;
fromMap(Map map);
Map<String, Object?> toMap()
}
/// Enum:The reason why region on the screen changed
enum TIMRegionChangeReason {
/// Triggered by user's gesture, such as double clicking, dragging or sliding the map
Gesture,
/// The event called by widget on the map, such as switching the map type by clicking the widget on the map
Event,
/// API interface event called by your program, such as re-setting map parameters and leads the changes in the map area.
APIs,
}
/// App information used as external navigation APP.
class NavigationMapItem{
/// APP name
final String name;
/// jumping function to navigation APP
final Function(double longitude, double latitude) jumpFunc;
NavigationMapItem(this.name, this.jumpFunc);
}
Inherit abstract classes and connect map SDK with plug-in business function #
Please inherit the following three abstract classed according with the Map SDK you selected.
TIMMapService
Getting current location and search for POI with both keyword and coordinate. Please implements these function with the Map SDK you selected.
/// 【Optional】 Please implement this function, only if you need use the positioning ability from Map SDK. Switch: 'isUseMapSDKLocation' of 'LocationPicker/LocationShow'.
/// Need getting the current location from Map SDK then move the map center to that location by 'moveMapCenter'.
/// Then, return the coordinate of the current location along with the list of POI around it.
void moveToCurrentLocationActionWithSearchPOIByMapSDK({
required void Function(TIMCoordinate coordinate) moveMapCenter,
void Function(TIMReverseGeoCodeSearchResult, bool)?
onGetReverseGeoCodeSearchResult,
});
/// Searching the POI list by keyword, those in designated city are of priority.
void poiCitySearch({
required void Function(List<TIMPoiInfo>?, bool)
onGetPoiCitySearchResult,
required String keyword,
required String city,
});
/// Searching the POI list according to the coordinate, and return a function with 'TIMReverseGeoCodeSearchResult' and boolean of isError.
void searchPOIByCoordinate(
{required TIMCoordinate coordinate,
required void Function(TIMReverseGeoCodeSearchResult, bool)
onGetReverseGeoCodeSearchResult});
TIMMapWidget
The basic class to render the Map widget you choose. This is a stateful widget, needs to inherit TIMMapState. Including the callback function after map loading done and map moving done.
final Function? onMapLoadDone;
final Function(TIMCoordinate? targetGeoPt, TIMRegionChangeReason regionChangeReason)? onMapMoveEnd;
TIMMapState
The state of basic class to render the Map widget you choose. Including several functions for the interaction with map, and returns the map widget can be used directly outside.
/// Callback function after map loading done
void onMapLoadDone(){}
/// Callback function after map moving
void onMapMoveEnd(TIMCoordinate? targetGeoPt, TIMRegionChangeReason regionChangeReason){}
/// Move the center coordinate of the map
void moveMapCenter(TIMCoordinate pt){}
/// Forbidden the map from interaction with gesture, etc.
void forbiddenMapFromInteract() {}
/// add a mark on the specific coordinate on map
void addMarkOnMap(TIMCoordinate pt, String title){}
/// getting the Map widget
@override
Widget build(BuildContext context) {
return Container(
child: the Map widget from the Map SDK you chosed(
onMapCreated: onMapCreated,
),
);
}
Using the LBS widgets #
Location picker(LocationPicker)
The interface and function are similar to those in WeChat. Users can positioning, choose a point on map, search for POI, show the POI list around.
/// The onChange(LocationMessage) callback after users finish choosing the location.
/// 【Reminder】The 'LocationMessage.desc' here splicing the name and address into a string, due to the location message of Tencent Cloud IM only includes one string, 'desc'.
/// Such as: "The University of Sydney/////Camperdown NSW 2006".
/// The splicing format can be parsed by anywhere in this plug-in.
final ValueChanged<LocationMessage> onChange;
/// LocationUtils with the TIMMapService implemented with specific Map SDK.
final LocationUtils locationUtils;
/// The default center coordinate shows before positioning.
final TIMCoordinate? initCoordinate;
/// To control if the poisoning ability from Map SDK is needed, if true, please make sure 'moveToCurrentLocationActionWithSearchPOIByMapSDK' been implemented correctly.
final bool? isUseMapSDKLocation;
/// TIMMapWidget with the inherited map widget by the Map SDK you chose.
final TIMMapWidget Function(
VoidCallback onMapLoadDone,
Key mapKey,
Function(TIMCoordinate? targetGeoPt,
TIMRegionChangeReason regionChangeReason)
onMapMoveEnd) mapBuilder;
Location shows in whole page(LocationShow)
The interface and function are similar to those in WeChat. Shows in big map with location mark. The location name and address show in bottom, with the button jumping to Map APP to navigate.
/// Address name
final String addressName;
/// Full address
final String? addressLocation;
/// Latitude
final double latitude;
/// Longitude
final double longitude;
/// LocationUtils with the TIMMapService implemented with specific Map SDK.
final LocationUtils locationUtils;
/// To control if the poisoning ability from Map SDK is needed, if true, please make sure 'moveToCurrentLocationActionWithSearchPOIByMapSDK' been implemented correctly.
final bool? isUseMapSDKLocation;
/// External navigation map list for jumping out to navigate, if nothing here, default list includes "Tencent Map", "AMap", "Baidu Map", "Apple Map".
final List<NavigationMapItem>? navigationMapList;
/// TIMMapWidget with the inherited map widget by the Map SDK you chose.
final TIMMapWidget Function(
VoidCallback onMapLoadDone,
Key mapKey) mapBuilder;
Location shows in history message list(LocationMsgElement)
This widget used to show LBS message in history message list in a smaller box. Contains location name, full address, and a small map.
/// message ID
final String? messageID;
/// V2TimLocationElem message
final V2TimLocationElem locationElem;
/// Whether this message is sent from self
final bool isFromSelf;
/// Whether shows the style represent this message is been jumped to
final bool? isShowJump;
/// Clear the jump function(commonly used with UIKit)
final VoidCallback? clearJump;
/// LocationUtils with the TIMMapService implemented with specific Map SDK.
final LocationUtils locationUtils;
/// To control if the poisoning ability from Map SDK is needed, if true, please make sure 'moveToCurrentLocationActionWithSearchPOIByMapSDK' been implemented correctly.
final bool? isUseMapSDKLocation;
/// TIMMapWidget with the inherited map widget by the Map SDK you chose.
final TIMMapWidget Function(VoidCallback onMapLoadDone, Key mapKey)
mapBuilder;
Implements with TIM Flutter TUIKit #
Though this part of code is optional, means you can use these widgets above wherever you need if you tend to self-implement integration, you can still read this part, to refer how to call these widgets.
Render message in history message list (LocationMsgElement)
Please add following parameter to 'TIMUIKitChat'. It will shows a small box in the history message list. By clicking the this item, it will jump to 'LocationShow' directly, so it's unnecessary to call 'LocationShow' inside your program in this case.
'TIMMapWidget' and 'TIMMapService' needs to be replaced by your inherited class with specific Map SDK implemented, like shows in our example.
locationMessageItemBuilder:
(locationElem, isFromSelf, isShowJump, clearJump, messageID) =>
LocationMsgElement(
messageID: messageID,
locationElem: locationElem,
isFromSelf: isFromSelf,
isShowJump: isShowJump,
clearJump: clearJump,
mapBuilder: (onMapLoadDone, mapKey) => TIMMapWidget(
onMapLoadDone: onMapLoadDone,
key: mapKey,
),
locationUtils: LocationUtils(TIMMapService()),
),
Add a 'Location' button in More Panel to call location picker(LocationPicker)
Please add following parameter to 'TIMUIKitChat'. This extraAction can jump to 'LocationPicker' and send LBS message.
'TIMMapWidget' and 'TIMMapService' needs to be replaced by your inherited class with specific Map SDK implemented, like shows in our example.
morePanelConfig: MorePanelConfig(
extraAction: [
MorePanelItem(
id: "location",
title: ("location"),
onTap: (c) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LocationPicker(
onChange: (LocationMessage location) async {
// The message sending function here needs to be modified according to your program.
final locationMessageInfo = await sdkInstance.v2TIMMessageManager.createLocationMessage(
desc: location.desc, longitude: location.longitude, latitude: location.latitude);
final messageInfo = locationMessageInfo.data!.messageInfo;
_timuiKitChatController.sendMessage(
convID: _getConvID()!,
convType: _getConvType(),
messageInfo: messageInfo
);
},
mapBuilder: (onMapLoadDone, mapKey, onMapMoveEnd) => TIMMapWidget(
onMapMoveEnd: onMapMoveEnd,
onMapLoadDone: onMapLoadDone,
key: mapKey,
),
locationUtils: LocationUtils(TIMMapService()),
),
));
},
icon: Container(
height: 64,
width: 64,
margin: const EdgeInsets.only(bottom: 4),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(5))),
child: Icon(
Icons.location_on,
color: hexToColor("5c6168"),
size: 32,
),
))
],
),
#中文版
腾讯云IM Flutter UIKit LBS位置消息能力插件 #
使用本插件,开发者可方便地在其腾讯云IM Flutter项目中,引入发送接收展示位置消息能力。
本插件提供三个组件:位置选择器(LocationPicker)/位置消息完整展示器(LocationShow)/位置消息列表展示器(LocationMsgElement)。并提供为其配套业务逻辑能力,除与地图底层SDK交互部分外。
为方便开发者快速接入,我们还提供基于百度地图的完整Example代码及百度地图快速接入指南(https://docs.qq.com/doc/DSXRBZG1CUGhIVEZx)。具体请查看腾讯云IM Flutter Demo或本Plugin的example。
术语介绍 #
POI: Point of interesting。即地图上任何非地理意义的有意义的点,每个POI均有特定坐标/名称/各级地址/ID。 例如:深圳腾讯大厦,蛇口太子广场。
开发接入 #
整体流程: #
- 选定底层地图SDK
- 继承三个抽象类,完成本插件业务代码与地图SDK间交互
- 将继承后的抽象类实例化,传入本插件提供的三个组件中
选定底层地图SDK #
本插件大部分业务逻辑接口基于百度地图设计,选用百度地图可较快完成项目。但不限制具体使用哪一块地图SDK,常用地图Flutter SDK如下:
百度地图:国内+国外(五万/年)数据。https://lbsyun.baidu.com/index.php?title=flutter/loc
高德地图:仅国内数据。https://lbs.amap.com/api/flutter/summary
Google Map:含全球数据。https://codelabs.developers.google.com/codelabs/google-maps-in-flutter#0
数据交互数据结构 #
本插件使用一系列数据结构来定义SDK与插件间传递的信息,包含如下:
class LocationMessage {
final String desc;
final double longitude;
final double latitude;
}
/// 代表经纬度
class TIMCoordinate implements TIMLocationBaseModel {
/// 纬度
late double latitude;
/// 经度
late double longitude;
Map<String, Object> toMap();
fromMap(Map map);
}
/// POI信息类
class TIMPoiInfo implements TIMLocationBaseModel {
/// POI名称
String? name;
/// POI坐标
TIMCoordinate? pt;
/// POI地址信息
String? address;
/// POI唯一标识符uid
String? uid;
/// POI所在省份
String? province;
/// POI所在城市
String? city;
fromMap(Map map);
Map<String, Object?> toMap();
}
/// 根据地理坐标反向查询结果类
class TIMReverseGeoCodeSearchResult implements TIMLocationBaseModel {
/// 地址坐标
TIMCoordinate? location;
/// 地址名称
String? address;
/// 层次化地址信息
TIMAddressComponent? addressDetail;
/// 地址周边POI信息,成员类型为BMKPoiInfo
List<TIMPoiInfo>? poiList;
/// 结合当前位置POI的语义化结果描述, 用于地址名称字段。例如"腾讯大厦内,招行信息研发大厦附近18米"。
String? semanticDescription;
fromMap(Map map);
Map<String, Object?> toMap();
}
/// 地址结果的层次化信息
class TIMAddressComponent implements TIMLocationBaseModel {
/// 国家
String? country;
/// 省份名称
String? province;
/// 城市名称
String? city;
/// 区县名称
String? district;
/// 乡镇
String? town;
fromMap(Map map);
Map<String, Object?> toMap()
}
/// 枚举:地图区域改变原因
enum TIMRegionChangeReason {
///<手势触发导致地图区域变化,如双击、拖拽、滑动地图
Gesture,
///<地图上控件事件,如点击指南针返回2D地图。
Event,
///<开发者调用接口、设置地图参数等导致地图区域变化
APIs,
}
/// 用于作为外部导航软件的App信息
class NavigationMapItem{
/// APP 名称
final String name;
/// 唤起外部导航APP的方法
final Function(double longitude, double latitude) jumpFunc;
NavigationMapItem(this.name, this.jumpFunc);
}
继承抽象类,连接地图SDK与插件业务逻辑 #
若选用百度地图,可直接使用我们的完整example代码,快速完成项目。详细指南:https://docs.qq.com/doc/DSXRBZG1CUGhIVEZx
请根据选定的地图SDK,继承以下三个类。
TIMMapService
地图定位及POI搜索能力Service。需要根据地图SDK完成交互并将数据提供给插件业务代码。
/// 【可选】仅当您需要使用地图SDK提供的定位能力,才需要继承本方法。开关:LocationPicker/LocationShow的isUseMapSDKLocation字段。
/// 需做到根据地图SDK提供的定位能力定位再再通过'moveMapCenter(coordinate)'将地图挪过去,
/// 并返回含根据新的地图中心查询附近POI的结果及是否出错参数的方法
void moveToCurrentLocationActionWithSearchPOIByMapSDK({
required void Function(TIMCoordinate coordinate) moveMapCenter,
void Function(TIMReverseGeoCodeSearchResult, bool)?
onGetReverseGeoCodeSearchResult,
});
/// 根据关键词搜索POI,优先返回在当前city内的结果,但同时也可以搜到其他city的POI
void poiCitySearch({
required void Function(List<TIMPoiInfo>?, bool)
onGetPoiCitySearchResult,
required String keyword,
required String city,
});
/// 根据地理坐标查询附近的POI,并返回包含TIMReverseGeoCodeSearchResult及是否报错参数的方法
void searchPOIByCoordinate(
{required TIMCoordinate coordinate,
required void Function(TIMReverseGeoCodeSearchResult, bool)
onGetReverseGeoCodeSearchResult});
TIMMapWidget
渲染地图的基类,对外暴露地图事件。是StatefulWidget,需要继承TIMMapState。 包含地图加载完成回调及地图拖动结束回调。
final Function? onMapLoadDone;
final Function(TIMCoordinate? targetGeoPt, TIMRegionChangeReason regionChangeReason)? onMapMoveEnd;
TIMMapState
渲染地图基类的State,提供完成一系列地图交互能力,并对外返回可直接使用的地图实例。
/// 地图创建完成回调
void onMapLoadDone(){}
/// 地图移动结束
void onMapMoveEnd(TIMCoordinate? targetGeoPt, TIMRegionChangeReason regionChangeReason){}
/// 移动地图视角
void moveMapCenter(TIMCoordinate pt){}
/// 禁用地图交互
void forbiddenMapFromInteract() {}
/// 在地图上添加图钉
void addMarkOnMap(TIMCoordinate pt, String title){}
/// 此处实例化地图
@override
Widget build(BuildContext context) {
return Container(
child: 某地图的Widget(
onMapCreated: onMapCreated,
mapOptions: initMapOptions(),
),
);
}
使用组件 #
位置选择器(LocationPicker)
该组件以类似微信的位置选择器页面呈现,允许用户定位当前位置/地图选点/搜索特定POI/展示选定POI周边其他POI。
/// 地理位置选择完成后的onChange事件,返回一个LocationMessage,可用于发送消息。
/// 【特别说明】由于腾讯云IM位置消息仅支持传递一个desc字符串,因此此处的LocationMessage.desc将名称及地址拼接传递,格式:"腾讯大厦/////深圳市南山区深南大道10000号"。
/// 该拼接格式可被本插件所需地方解析,请放心使用。
final ValueChanged<LocationMessage> onChange;
/// 传入根据选定地图SDK实例化后的LocationUtils
final LocationUtils locationUtils;
/// 用于还未加载出来定位时,打开页面后,默认的中心点。
final TIMCoordinate? initCoordinate;
/// 用于控制是否使用地图SDK定位能力。若使用,请确保moveToCurrentLocationActionWithSearchPOIByMapSDK方法继承正确。
final bool? isUseMapSDKLocation;
/// 传入根据选定地图SDK实例化后的地图组件TIMMapWidget
final TIMMapWidget Function(
VoidCallback onMapLoadDone,
Key mapKey,
Function(TIMCoordinate? targetGeoPt,
TIMRegionChangeReason regionChangeReason)
onMapMoveEnd) mapBuilder;
位置消息完整展示器(LocationShow)
该组件以类似微信的位置详情展示页面呈现,使用大地图配上图标,展示接收到的位置。底部显示位置名称/地址,及拉起跳转导航软件的按钮。
/// 位置名称标题
final String addressName;
/// 位置地址
final String? addressLocation;
/// 纬度
final double latitude;
/// 经度
final double longitude;
/// 传入根据选定地图SDK实例化后的LocationUtils
final LocationUtils locationUtils;
/// 用于控制是否使用地图SDK定位能力。若使用,请确保moveToCurrentLocationActionWithSearchPOIByMapSDK方法继承正确。
final bool? isUseMapSDKLocation;
/// 第三方导航APP列表,如果没传,则默认腾讯/百度/高德/苹果地图。
final List<NavigationMapItem>? navigationMapList;
/// 传入根据选定地图SDK实例化后的地图组件TIMMapWidget
final TIMMapWidget Function(
VoidCallback onMapLoadDone,
Key mapKey) mapBuilder;
位置消息列表展示器(LocationMsgElement)
此组件用于在历史消息列表中,展示位置消息。效果类似微信。 提供小地图展示/位置名称及地址展示。
/// 消息ID
final String? messageID;
/// V2TimLocationElem消息
final V2TimLocationElem locationElem;
/// 是否自己发送
final bool isFromSelf;
/// 是否显示被跳转样式
final bool? isShowJump;
/// 清除跳转方法
final VoidCallback? clearJump;
/// 用于控制是否使用地图SDK定位能力。若使用,请确保moveToCurrentLocationActionWithSearchPOIByMapSDK方法继承正确。
final bool? isUseMapSDKLocation;
/// 传入根据选定地图SDK实例化后的LocationUtils
final LocationUtils locationUtils;
/// 传入根据选定地图SDK实例化后的地图组件TIMMapWidget
final TIMMapWidget Function(VoidCallback onMapLoadDone, Key mapKey)
mapBuilder;
配合UIKit使用接入 #
此部分完整代码可在IM Flutter Demo中得到。
历史消息列表渲染小地图(LocationMsgElement)
请在TIMUIKitChat中新增如下字段。该小地图可以自动点击跳转至完整版详细地图(LocationShow)。
TIMMapWidget和TIMMapService需要替换为自己的实例化对象。
locationMessageItemBuilder:
(locationElem, isFromSelf, isShowJump, clearJump, messageID) =>
LocationMsgElement(
messageID: messageID,
locationElem: locationElem,
isFromSelf: isFromSelf,
isShowJump: isShowJump,
clearJump: clearJump,
mapBuilder: (onMapLoadDone, mapKey) => TIMMapWidget(
onMapLoadDone: onMapLoadDone,
key: mapKey,
),
locationUtils: LocationUtils(TIMMapService()),
),
加号面板增加位置item并跳转至位置选择器(LocationMsgElement)
请在TIMUIKitChat中新增如下字段。该extraAction可跳转至位置选择器,并发送消息。
TIMMapWidget和TIMMapService需要替换为自己的实例化对象。
morePanelConfig: MorePanelConfig(
extraAction: [
MorePanelItem(
id: "location",
title: ("位置"),
onTap: (c) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LocationPicker(
onChange: (LocationMessage location) async {
// 此处消息发送逻辑需要根据业务框架适当修改
final locationMessageInfo = await sdkInstance.v2TIMMessageManager.createLocationMessage(
desc: location.desc, longitude: location.longitude, latitude: location.latitude);
final messageInfo = locationMessageInfo.data!.messageInfo;
_timuiKitChatController.sendMessage(
convID: _getConvID()!,
convType: _getConvType(),
messageInfo: messageInfo
);
},
mapBuilder: (onMapLoadDone, mapKey, onMapMoveEnd) => TIMMapWidget(
onMapMoveEnd: onMapMoveEnd,
onMapLoadDone: onMapLoadDone,
key: mapKey,
),
locationUtils: LocationUtils(TIMMapService()),
),
));
},
icon: Container(
height: 64,
width: 64,
margin: const EdgeInsets.only(bottom: 4),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(5))),
child: Icon(
Icons.location_on,
color: hexToColor("5c6168"),
size: 32,
),
))
],
),