common_point_select_address_page.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. import 'dart:io';
  2. import 'package:flutter/cupertino.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter/src/widgets/framework.dart';
  5. import 'package:flutter_map/flutter_map.dart';
  6. import 'package:flutter_screenutil/flutter_screenutil.dart';
  7. import 'package:get/get.dart';
  8. import 'package:get/get_core/src/get_main.dart';
  9. import 'package:location/base/base_page.dart';
  10. import 'package:location/resource/assets.gen.dart';
  11. import 'package:location/resource/colors.gen.dart';
  12. import 'package:location/resource/string.gen.dart';
  13. import 'package:location/utils/common_expand.dart';
  14. import 'package:pull_to_refresh/pull_to_refresh.dart';
  15. import 'package:sliding_sheet2/sliding_sheet2.dart';
  16. import '../../../router/app_pages.dart';
  17. import '../../../widget/common_view.dart';
  18. import 'common_point_select_address_controller.dart';
  19. class CommonPointSelectAddressPage
  20. extends BasePage<CommonPointSelectAddressController> {
  21. const CommonPointSelectAddressPage({super.key});
  22. static Future<Map<String, dynamic>?> start() async {
  23. final result = await Get.toNamed(RoutePath.commonPointSelectAddress);
  24. if (result is Map<String, dynamic>) {
  25. return result;
  26. }
  27. return null;
  28. }
  29. @override
  30. bool immersive() {
  31. return true;
  32. }
  33. @override
  34. Widget buildBody(BuildContext context) {
  35. return PopScope(
  36. canPop: false,
  37. onPopInvokedWithResult: (bool didPop, dynamic result) {
  38. if (didPop) {
  39. return;
  40. }
  41. controller.backClick();
  42. },
  43. child: Stack(
  44. children: [
  45. buildMapView(),
  46. buildHeadBgView(),
  47. buildMapLogoView(),
  48. buildLocationFunView(),
  49. buildSlidingSheetView(),
  50. buildToolbarView(),
  51. ],
  52. ),
  53. );
  54. }
  55. Widget buildLocationFunView() {
  56. return Obx(() {
  57. return Positioned(
  58. right: 12.w,
  59. bottom: 0.48.sh + controller.sheetProgress * 0.48.sh,
  60. child: Opacity(
  61. opacity: 1 - controller.sheetProgress,
  62. child: GestureDetector(
  63. onTap: controller.moveToCurrentLocation,
  64. child: Container(
  65. width: 38.w,
  66. height: 38.w,
  67. decoration: BoxDecoration(
  68. color: ColorName.white,
  69. borderRadius: BorderRadius.circular(10.r),
  70. boxShadow: [
  71. BoxShadow(
  72. color: ColorName.black10,
  73. blurRadius: 16,
  74. spreadRadius: 2,
  75. offset: Offset(0, 4.w),
  76. ),
  77. ],
  78. ),
  79. child: Center(
  80. child: Assets.images.iconMainLocation
  81. .image(width: 24.w, height: 24.w),
  82. )),
  83. ),
  84. ),
  85. );
  86. });
  87. }
  88. Widget buildMapLogoView() {
  89. return Visibility(
  90. visible: Platform.isAndroid,
  91. child: Obx(() {
  92. return Positioned(
  93. left: 12.w,
  94. bottom: 0.465.sh + controller.sheetProgress * 0.48.sh,
  95. child: Opacity(
  96. opacity: 1 - controller.sheetProgress,
  97. child: Column(
  98. crossAxisAlignment: CrossAxisAlignment.start,
  99. children: [
  100. Assets.images.iconAmapLogo.image(height: 20.w),
  101. Text(StringName.locationAmapCo,
  102. style: TextStyle(
  103. fontSize: 9.sp, color: '#666666'.color, height: 1))
  104. ],
  105. ),
  106. ));
  107. }),
  108. );
  109. }
  110. Widget buildSlidingSheetView() {
  111. return SizedBox(
  112. width: double.infinity,
  113. height: 1.sh,
  114. child: SlidingSheet(
  115. listener: (SheetState state) {
  116. controller.setSheetProgress(state.progress);
  117. },
  118. controller: controller.sheetController,
  119. color: ColorName.transparent,
  120. elevation: 0,
  121. shadowColor: ColorName.transparent,
  122. cornerRadius: 0,
  123. snapSpec: SnapSpec(
  124. initialSnap: 0.45,
  125. // Enable snapping. This is true by default.
  126. snap: true,
  127. // Set custom snapping points.
  128. snappings: [0.45, 0.94],
  129. // Define to what the snappings relate to. In this case,
  130. // the total available space that the sheet can expand to.
  131. positioning: SnapPositioning.relativeToAvailableSpace,
  132. ),
  133. headerBuilder: selectAddressHeaderBuilder,
  134. customBuilder: selectAddressCustomBuilder),
  135. );
  136. }
  137. Widget buildToolbarView() {
  138. return SafeArea(
  139. child: Container(
  140. padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 14.5.w),
  141. child: Row(
  142. children: [
  143. GestureDetector(
  144. onTap: controller.backClick,
  145. child: CommonView.getBackBtnView()),
  146. Spacer(),
  147. GestureDetector(
  148. onTap: controller.onSelectAddressDone,
  149. child: Container(
  150. width: 50.w,
  151. height: 28.w,
  152. decoration: BoxDecoration(
  153. color: ColorName.white,
  154. borderRadius: BorderRadius.circular(8.r),
  155. ),
  156. child: Center(
  157. child: Text(StringName.selectAddressDone,
  158. style: TextStyle(
  159. fontSize: 12.sp,
  160. color: ColorName.black90,
  161. fontWeight: FontWeight.bold)),
  162. )),
  163. ),
  164. ],
  165. ),
  166. ),
  167. );
  168. }
  169. Widget buildHeadBgView() {
  170. return IgnorePointer(
  171. child: AspectRatio(
  172. aspectRatio: 360 / 146,
  173. child: Assets.images.bgCommonPointSelectAddressTop.image(
  174. width: double.infinity,
  175. ),
  176. ),
  177. );
  178. }
  179. Widget selectAddressCustomBuilder(BuildContext context,
  180. ScrollController scrollController, SheetState state) {
  181. return Container(
  182. color: Colors.white,
  183. height: double.infinity,
  184. child: SmartRefresher(
  185. enablePullUp: true,
  186. enablePullDown: false,
  187. controller: controller.refreshController,
  188. onLoading: controller.onLoadMorePoiData,
  189. child: CustomScrollView(
  190. controller: scrollController,
  191. slivers: [
  192. SliverToBoxAdapter(child: buildRangeCenterLocationView()),
  193. Obx(() {
  194. return SliverList.builder(
  195. itemBuilder: (ctx, index) =>
  196. buildPoiSearchItem(controller.poiList[index]),
  197. itemCount: controller.poiList.length);
  198. })
  199. ],
  200. ),
  201. ),
  202. );
  203. }
  204. Widget buildRangeCenterLocationView() {
  205. return Container(
  206. decoration: BoxDecoration(
  207. color: '#F2F5FE'.color,
  208. borderRadius: BorderRadius.circular(8.r),
  209. border: Border.all(
  210. color: '#D0D9F4'.color,
  211. width: 0.5.w,
  212. ),
  213. ),
  214. margin: EdgeInsets.only(left: 12.w, right: 12.w, bottom: 8.w, top: 4.w),
  215. padding: EdgeInsets.symmetric(horizontal: 13.w, vertical: 8.w),
  216. child: Row(
  217. crossAxisAlignment: CrossAxisAlignment.start,
  218. children: [
  219. Container(
  220. margin: EdgeInsets.only(top: 3.w),
  221. child:
  222. Assets.images.iconSelectCurrentLocation.image(width: 10.w)),
  223. SizedBox(width: 8.w),
  224. Column(
  225. crossAxisAlignment: CrossAxisAlignment.start,
  226. children: [
  227. Text(StringName.currentLocation,
  228. style: TextStyle(
  229. fontSize: 12.sp,
  230. color: ColorName.black90,
  231. fontWeight: FontWeight.bold)),
  232. SizedBox(height: 4.w),
  233. Obx(() {
  234. return Text(
  235. controller.currentRangeCenterLocation?.address ??
  236. StringName.selectAddressPlease,
  237. style:
  238. TextStyle(fontSize: 10.sp, color: ColorName.black50));
  239. })
  240. ],
  241. )
  242. ],
  243. ),
  244. );
  245. }
  246. Widget selectAddressHeaderBuilder(BuildContext context, SheetState state) {
  247. return IntrinsicHeight(
  248. child: Column(
  249. children: [
  250. buildRangeView(),
  251. SizedBox(height: 12.w),
  252. buildSelectAddressView()
  253. ],
  254. ),
  255. );
  256. }
  257. Widget buildSelectAddressView() {
  258. return GestureDetector(
  259. onTap: controller.onOpenSearchAddressModel,
  260. child: Container(
  261. padding: EdgeInsets.all(12.w),
  262. width: double.infinity,
  263. decoration: BoxDecoration(
  264. color: ColorName.white,
  265. borderRadius: BorderRadius.only(
  266. topLeft: Radius.circular(18.r),
  267. topRight: Radius.circular(18.r))),
  268. child: Obx(() {
  269. return Container(
  270. width: double.infinity,
  271. height: 40.w,
  272. decoration: BoxDecoration(
  273. border: controller.sheetProgress == 1
  274. ? Border.all(color: '#EAECEE'.color, width: 0.5.w)
  275. : null,
  276. color: '#F6F6F8'.color,
  277. borderRadius: BorderRadius.circular(8.r)),
  278. child: Row(
  279. children: [
  280. SizedBox(width: 12.w),
  281. Assets.images.iconCommonPointSearch
  282. .image(width: 14.w, height: 14.w),
  283. SizedBox(width: 6.w),
  284. Expanded(
  285. child: TextField(
  286. enabled: controller.sheetProgress == 1,
  287. controller: controller.searchEditController,
  288. focusNode: controller.searchFocusNode,
  289. style: TextStyle(
  290. fontSize: 14.sp, color: ColorName.primaryTextColor),
  291. maxLines: 1,
  292. maxLength: 30,
  293. keyboardType: TextInputType.text,
  294. textAlignVertical: TextAlignVertical.center,
  295. textInputAction: TextInputAction.next,
  296. onChanged: (txt) => controller.onSearchTextChange(txt),
  297. decoration: InputDecoration(
  298. hintText:
  299. StringName.selectAddressPleaseEnterPlaceName,
  300. counterText: '',
  301. hintStyle: TextStyle(
  302. fontSize: 13.sp, color: ColorName.black40),
  303. contentPadding: const EdgeInsets.all(0),
  304. border: const OutlineInputBorder(
  305. borderSide: BorderSide.none)))),
  306. SizedBox(width: 12.w),
  307. ],
  308. ),
  309. );
  310. }),
  311. ),
  312. );
  313. }
  314. Widget buildRangeView() {
  315. return Obx(() {
  316. return Opacity(
  317. opacity: 1 - controller.sheetProgress,
  318. child: Container(
  319. width: 336.w,
  320. height: 41.w,
  321. decoration: BoxDecoration(
  322. color: ColorName.white,
  323. borderRadius: BorderRadius.circular(12.r)),
  324. child: Builder(builder: (context) {
  325. return Row(
  326. children: [
  327. SizedBox(width: 16.w),
  328. Text(StringName.selectAddressCommonlyUsedRange,
  329. style:
  330. TextStyle(fontSize: 12.sp, color: ColorName.black80)),
  331. SizedBox(width: 8.w),
  332. Expanded(
  333. child: SliderTheme(
  334. data: SliderTheme.of(context).copyWith(
  335. thumbColor: Colors.white,
  336. thumbShape: CustomBlueThumb(
  337. thumbRadius: 4.6.w,
  338. outerRingWidth: 2.5.w,
  339. color: ColorName.colorPrimary,
  340. ),
  341. overlayShape: SliderComponentShape.noOverlay,
  342. trackHeight: 6.w,
  343. activeTrackColor: ColorName.colorPrimary,
  344. inactiveTrackColor: "#E4E4E4".color,
  345. trackShape: CustomTrackShape(),
  346. ),
  347. child: Obx(() {
  348. return Slider(
  349. value: controller.commonPointRange,
  350. min: 50,
  351. max: 500,
  352. onChanged: (value) {
  353. controller.setCommonPointRange(value);
  354. },
  355. onChangeStart: (value) {
  356. controller.setCommonPointRangeStart();
  357. },
  358. onChangeEnd: (value) {
  359. controller.setCommonPointRangeEnd();
  360. },
  361. );
  362. }),
  363. ),
  364. ),
  365. SizedBox(width: 8.w),
  366. Container(
  367. width: 48.w,
  368. height: 21.w,
  369. decoration: BoxDecoration(
  370. color: '#F6F6F8'.color,
  371. borderRadius: BorderRadius.circular(5.w)),
  372. child: Center(child: Obx(() {
  373. return Text(
  374. '${controller.commonPointRange.toInt()}m',
  375. style:
  376. TextStyle(fontSize: 12.sp, color: ColorName.black80),
  377. );
  378. })),
  379. ),
  380. SizedBox(width: 16.w),
  381. ],
  382. );
  383. }),
  384. ),
  385. );
  386. });
  387. }
  388. Widget buildMapView() {
  389. return SizedBox(
  390. width: double.infinity,
  391. height: 0.65.sh,
  392. child: Stack(
  393. children: [
  394. MapWidget(controller: controller.mapController),
  395. Center(
  396. child: Container(
  397. width: 18.w,
  398. height: 18.w,
  399. decoration: BoxDecoration(
  400. shape: BoxShape.circle,
  401. color: ColorName.colorPrimary,
  402. border: Border.all(color: ColorName.white, width: 2.w),
  403. ),
  404. ),
  405. )
  406. ],
  407. ));
  408. }
  409. Widget buildPoiSearchItem(PoiItem item) {
  410. return GestureDetector(
  411. onTap: () {
  412. controller.onPoiItemClick(item);
  413. },
  414. child: Container(
  415. margin: EdgeInsets.only(left: 12.w, right: 12.w, bottom: 8.w, top: 4.w),
  416. padding: EdgeInsets.only(left: 13.w, top: 8.w, bottom: 8.w),
  417. child: Row(
  418. crossAxisAlignment: CrossAxisAlignment.start,
  419. children: [
  420. Container(
  421. margin: EdgeInsets.only(top: 3.w),
  422. child: Assets.images.iconPoiLocation.image(width: 10.w)),
  423. SizedBox(width: 8.w),
  424. Expanded(
  425. child: IntrinsicHeight(
  426. child: Column(
  427. crossAxisAlignment: CrossAxisAlignment.start,
  428. children: [
  429. Row(
  430. children: [
  431. Expanded(
  432. child: Text(item.title ?? '',
  433. overflow: TextOverflow.ellipsis,
  434. style: TextStyle(
  435. fontSize: 12.sp,
  436. color: ColorName.black90,
  437. fontWeight: FontWeight.bold)),
  438. ),
  439. SizedBox(width: 16.w),
  440. buildDistanceTextView(item)
  441. ],
  442. ),
  443. SizedBox(height: 4.w),
  444. Text(item.address ?? '',
  445. style: TextStyle(
  446. fontSize: 10.sp, color: ColorName.black50))
  447. ],
  448. ),
  449. ),
  450. )
  451. ],
  452. ),
  453. ),
  454. );
  455. }
  456. Widget buildDistanceTextView(PoiItem item) {
  457. final location = controller.mineUserInfo.lastLocation.value;
  458. if (location == null ||
  459. location.latitude == null ||
  460. location.longitude == null ||
  461. item.longitude == null ||
  462. item.latitude == null) {
  463. return SizedBox.shrink();
  464. }
  465. final distance = MapUtil.calculateLineDistance(location.longitude!,
  466. location.latitude!, item.longitude!, item.latitude!);
  467. return Text(MapUtil.format(distance),
  468. style: TextStyle(fontSize: 10.sp, color: ColorName.black30));
  469. }
  470. }
  471. class CustomTrackShape extends RoundedRectSliderTrackShape {
  472. @override
  473. Rect getPreferredRect({
  474. required RenderBox parentBox,
  475. Offset offset = Offset.zero,
  476. required SliderThemeData sliderTheme,
  477. bool isEnabled = false,
  478. bool isDiscrete = false,
  479. }) {
  480. final trackHeight = sliderTheme.trackHeight;
  481. final trackLeft = offset.dx;
  482. final trackTop = offset.dy + (parentBox.size.height - trackHeight!) / 2;
  483. final trackWidth = parentBox.size.width;
  484. return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
  485. }
  486. }
  487. class CustomBlueThumb extends SliderComponentShape {
  488. final double thumbRadius;
  489. final double outerRingWidth;
  490. final Color color;
  491. const CustomBlueThumb({
  492. this.thumbRadius = 12.0,
  493. this.outerRingWidth = 3.0,
  494. this.color = const Color(0xFF4476FF),
  495. });
  496. @override
  497. Size getPreferredSize(bool isEnabled, bool isDiscrete) {
  498. return Size.fromRadius(thumbRadius + outerRingWidth);
  499. }
  500. @override
  501. void paint(
  502. PaintingContext context,
  503. Offset center, {
  504. required Animation<double> activationAnimation,
  505. required Animation<double> enableAnimation,
  506. required bool isDiscrete,
  507. required TextPainter labelPainter,
  508. required RenderBox parentBox,
  509. required SliderThemeData sliderTheme,
  510. required TextDirection textDirection,
  511. required double value,
  512. required double textScaleFactor,
  513. required Size sizeWithOverflow,
  514. }) {
  515. final Canvas canvas = context.canvas;
  516. final Paint outerPaint = Paint()
  517. ..color = color // 使用您指定的蓝色
  518. ..strokeWidth = outerRingWidth
  519. ..style = PaintingStyle.stroke;
  520. final Paint innerPaint = Paint()
  521. ..color = Colors.white
  522. ..style = PaintingStyle.fill;
  523. // 绘制蓝色外圈
  524. canvas.drawCircle(center, thumbRadius + outerRingWidth / 2, outerPaint);
  525. // 绘制白色内圆
  526. canvas.drawCircle(center, thumbRadius, innerPaint);
  527. }
  528. }