import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/modules/room/background/room_background_apply_service.dart'; import 'package:yumi/modules/room/background/room_background_assets.dart'; import 'package:yumi/modules/room/background/room_background_history.dart'; import 'package:yumi/modules/room/voice_room_route.dart'; import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/shared/business_logic/usecases/sc_fixed_width_tabIndicator.dart'; import 'package:yumi/shared/tools/sc_loading_manager.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart'; import 'package:yumi/ui_kit/theme/socialchat_theme.dart'; class RoomBackgroundSelectPage extends StatefulWidget { const RoomBackgroundSelectPage({super.key}); @override State createState() => _RoomBackgroundSelectPageState(); } class _RoomBackgroundSelectPageState extends State with SingleTickerProviderStateMixin { late final TabController _tabController; late final List<_RoomBackgroundItem> _officialItems; late final List<_RoomBackgroundItem> _mineItems; bool get _showAddButton => _tabController.index == 1; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); _tabController.addListener(_handleTabChange); final currentBackground = _currentRoomBackground(); _officialItems = roomBackgroundExampleAssets .map( (path) => _RoomBackgroundItem( imagePath: path, inUse: path == currentBackground, ), ) .toList(); final historyItems = RoomBackgroundHistory.load(); if (currentBackground.isNotEmpty && !isRoomBackgroundExampleAsset(currentBackground) && !historyItems.contains(currentBackground)) { historyItems.insert(0, currentBackground); RoomBackgroundHistory.add(currentBackground); } _mineItems = historyItems .map( (path) => _RoomBackgroundItem( imagePath: path, inUse: path == currentBackground, ), ) .toList(); } @override void dispose() { _tabController.removeListener(_handleTabChange); _tabController.dispose(); super.dispose(); } void _handleTabChange() { if (!mounted) { return; } setState(() {}); } Future _openUploadPage() async { final uploadedPath = await VoiceRoomRoute.openRoomBackgroundUpload( context, ); if (!mounted || (uploadedPath ?? "").trim().isEmpty) { return; } setState(() { _markInUse(uploadedPath!); _upsertMineItem(uploadedPath, inUse: true); }); _tabController.animateTo(1); } Future _previewItem(List<_RoomBackgroundItem> items, int index) async { final shouldUse = await VoiceRoomRoute.openRoomBackgroundPreview( context, backgroundPath: items[index].imagePath, ); if (!mounted || shouldUse != true) { return; } await _applyItem(items, index); } Future _applyItem(List<_RoomBackgroundItem> items, int index) async { final item = items[index]; final imagePath = item.imagePath.trim(); if (imagePath.isEmpty) { return; } final localizations = SCAppLocalizations.of(context)!; SCLoadingManager.show(context: context); try { final appliedUrl = await RoomBackgroundApplyService.apply( context, imagePath, ); if (!mounted) { return; } setState(() { _markInUse(imagePath); _upsertMineItem(appliedUrl, inUse: items == _mineItems); }); SCTts.show(localizations.operationSuccessful); } catch (e, stackTrace) { if (mounted) { SCTts.show(localizations.operationFail); } } finally { SCLoadingManager.hide(); } } String _currentRoomBackground() { return context .read() .currenRoom ?.roomProfile ?.roomProfile ?.roomBackground ?.trim() ?? ""; } void _markInUse(String imagePath) { final normalized = imagePath.trim(); for (final item in [..._officialItems, ..._mineItems]) { item.inUse = item.imagePath == normalized; } } void _upsertMineItem(String imagePath, {bool inUse = false}) { final normalized = imagePath.trim(); if (normalized.isEmpty) { return; } _mineItems.removeWhere((item) => item.imagePath == normalized); _mineItems.insert(0, _RoomBackgroundItem(imagePath: normalized)); if (inUse) { _markInUse(normalized); } } @override Widget build(BuildContext context) { final localizations = SCAppLocalizations.of(context)!; final bottomPadding = MediaQuery.of(context).padding.bottom; return Stack( children: [ Image.asset( SCGlobalConfig.businessLogicStrategy.getLanguagePageBackgroundImage(), width: ScreenUtil().screenWidth, height: ScreenUtil().screenHeight, fit: BoxFit.fill, ), Scaffold( backgroundColor: Colors.transparent, appBar: _buildAppBar(context, localizations), body: Container( color: const Color(0xff0F0F0F), child: Stack( children: [ Column( children: [ Container( height: 0.5, color: Colors.white.withValues(alpha: 0.15), ), Expanded( child: TabBarView( controller: _tabController, children: [ _RoomBackgroundGrid( items: _officialItems, onTap: (index) => _previewItem(_officialItems, index), bottomPadding: 24.w, ), _RoomBackgroundGrid( items: _mineItems, onTap: (index) => _previewItem(_mineItems, index), bottomPadding: 100.w, ), ], ), ), ], ), if (_showAddButton) Positioned( left: 18.w, right: 18.w, bottom: 20.w + bottomPadding, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: _openUploadPage, child: Container( height: 48.w, alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(999.w), gradient: LinearGradient( colors: [ SocialChatTheme.primaryLight, const Color(0xff8BF2D0), ], ), ), child: Text( 'add', style: TextStyle( color: const Color(0xff0B2823), fontSize: 16.sp, fontWeight: FontWeight.w700, ), ), ), ), ), ], ), ), ), ], ); } PreferredSizeWidget _buildAppBar( BuildContext context, SCAppLocalizations localizations, ) { return SocialChatAppBar( backgroundColor: Colors.transparent, child: Row( children: [ SizedBox( width: 52.w, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.pop(context), child: Icon( SCGlobalConfig.lang == "ar" ? Icons.keyboard_arrow_right : Icons.keyboard_arrow_left, size: 28.w, color: Colors.white, ), ), ), Expanded( child: Center( child: SizedBox( width: 180.w, child: TabBar( controller: _tabController, labelColor: Colors.white, unselectedLabelColor: Colors.white54, overlayColor: const WidgetStatePropertyAll( Colors.transparent, ), labelStyle: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, ), unselectedLabelStyle: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w500, ), indicator: SCFixedWidthTabIndicator( width: 18.w, color: SocialChatTheme.primaryLight, height: 3.w, borderRadius: 2.w, ), dividerColor: Colors.transparent, splashFactory: NoSplash.splashFactory, tabs: [ Tab(text: localizations.official), Tab(text: localizations.mine), ], ), ), ), ), SizedBox( width: 72.w, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: _openUploadPage, child: Align( alignment: Alignment.center, child: Text( localizations.custom, style: TextStyle( color: SocialChatTheme.primaryLight, fontSize: 15.sp, fontWeight: FontWeight.w600, ), ), ), ), ), ], ), ); } } class _RoomBackgroundGrid extends StatelessWidget { const _RoomBackgroundGrid({ required this.items, required this.onTap, required this.bottomPadding, }); final List<_RoomBackgroundItem> items; final ValueChanged onTap; final double bottomPadding; @override Widget build(BuildContext context) { return GridView.builder( padding: EdgeInsets.fromLTRB(16.w, 16.w, 16.w, bottomPadding), itemCount: items.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, mainAxisSpacing: 14.w, crossAxisSpacing: 14.w, childAspectRatio: 0.64, ), itemBuilder: (context, index) { return _RoomBackgroundCard( item: items[index], onTap: () => onTap(index), ); }, ); } } class _RoomBackgroundCard extends StatelessWidget { const _RoomBackgroundCard({required this.item, required this.onTap}); final _RoomBackgroundItem item; final VoidCallback onTap; @override Widget build(BuildContext context) { final localizations = SCAppLocalizations.of(context)!; return GestureDetector( onTap: onTap, child: Container( decoration: BoxDecoration( color: const Color(0xff18F2B1).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12.w), border: Border.all( color: item.inUse ? SocialChatTheme.primaryLight : Colors.white.withValues(alpha: 0.08), width: 1.w, ), ), child: Stack( children: [ Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( child: Padding( padding: EdgeInsets.all(10.w), child: ClipRRect( borderRadius: BorderRadius.circular(10.w), child: _buildPreview(context), ), ), ), Padding( padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 12.w), child: Text( localizations.staticOrGifImage, textAlign: TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: Colors.white, fontSize: 13.sp, fontWeight: FontWeight.w500, ), ), ), ], ), if (item.inUse) PositionedDirectional( top: 10.w, start: 10.w, child: Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.w), decoration: BoxDecoration( color: SocialChatTheme.primaryLight, borderRadius: BorderRadius.circular(999.w), ), child: Text( localizations.inUse, style: TextStyle( color: const Color(0xff09372E), fontSize: 11.sp, fontWeight: FontWeight.w700, ), ), ), ), ], ), ), ); } Widget _buildPreview(BuildContext context) { final localizations = SCAppLocalizations.of(context)!; final imagePath = item.imagePath.trim(); if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) { return Image.network( imagePath, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _buildEmptyPreview(localizations), ); } if (imagePath.startsWith("assets/") || imagePath.startsWith("sc_images/")) { return Image.asset( imagePath, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _buildEmptyPreview(localizations), ); } if (imagePath.isNotEmpty) { final file = File(imagePath); if (file.existsSync()) { return Image.file(file, fit: BoxFit.cover); } } return _buildEmptyPreview(localizations); } Widget _buildEmptyPreview(SCAppLocalizations localizations) { return DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [const Color(0xff0F4C40), const Color(0xff0B2823)], ), ), child: Stack( children: [ Center( child: Icon( Icons.image_outlined, size: 34.w, color: Colors.white.withValues(alpha: 0.45), ), ), Center( child: Padding( padding: EdgeInsets.symmetric(horizontal: 12.w), child: Text( localizations.staticOrGifImage, textAlign: TextAlign.center, style: TextStyle( color: Colors.white.withValues(alpha: 0.72), fontSize: 12.sp, fontWeight: FontWeight.w500, ), ), ), ), ], ), ); } } class _RoomBackgroundItem { _RoomBackgroundItem({required this.imagePath, this.inUse = false}); final String imagePath; bool inUse; }