common_point_select_address_page.dart 18 KB

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