515 lines
16 KiB
Dart
515 lines
16 KiB
Dart
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<RoomBackgroundSelectPage> createState() =>
|
|
_RoomBackgroundSelectPageState();
|
|
}
|
|
|
|
class _RoomBackgroundSelectPageState extends State<RoomBackgroundSelectPage>
|
|
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<void> _openUploadPage() async {
|
|
final uploadedPath = await VoiceRoomRoute.openRoomBackgroundUpload<String>(
|
|
context,
|
|
);
|
|
if (!mounted || (uploadedPath ?? "").trim().isEmpty) {
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_markInUse(uploadedPath!);
|
|
_upsertMineItem(uploadedPath, inUse: true);
|
|
});
|
|
_tabController.animateTo(1);
|
|
}
|
|
|
|
Future<void> _previewItem(List<_RoomBackgroundItem> items, int index) async {
|
|
final shouldUse = await VoiceRoomRoute.openRoomBackgroundPreview<bool>(
|
|
context,
|
|
backgroundPath: items[index].imagePath,
|
|
);
|
|
if (!mounted || shouldUse != true) {
|
|
return;
|
|
}
|
|
await _applyItem(items, index);
|
|
}
|
|
|
|
Future<void> _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<RtcProvider>()
|
|
.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<Color>(
|
|
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<int> 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;
|
|
}
|