mirror of
https://github.com/Ujwal223/FocusGram.git
synced 2026-04-03 01:50:45 +02:00
- Reordered Settings Page. - Added "Click to Unblur" for posts. - Added Persistent Notification - Improved Grayscale Scheduling. and more.
533 lines
17 KiB
Dart
533 lines
17 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
enum SkeletonType { feed, reels, explore, messages, profile, generic }
|
|
|
|
class SkeletonScreen extends StatefulWidget {
|
|
final SkeletonType skeletonType;
|
|
|
|
const SkeletonScreen({super.key, this.skeletonType = SkeletonType.generic});
|
|
|
|
@override
|
|
State<SkeletonScreen> createState() => _SkeletonScreenState();
|
|
}
|
|
|
|
class _SkeletonScreenState extends State<SkeletonScreen>
|
|
with SingleTickerProviderStateMixin {
|
|
late AnimationController _shimmerController;
|
|
late Animation<double> _shimmerAnimation;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_shimmerController = AnimationController(
|
|
duration: const Duration(milliseconds: 1200),
|
|
vsync: this,
|
|
)..repeat();
|
|
_shimmerAnimation = Tween<double>(
|
|
begin: -1.0,
|
|
end: 2.0,
|
|
).animate(_shimmerController);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_shimmerController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final baseColor = theme.colorScheme.surfaceContainerHighest.withValues(
|
|
alpha: theme.brightness == Brightness.dark ? 0.25 : 0.4,
|
|
);
|
|
final highlightColor = theme.colorScheme.onSurface.withValues(alpha: 0.08);
|
|
|
|
return Container(
|
|
color: theme.scaffoldBackgroundColor,
|
|
width: double.infinity,
|
|
height: double.infinity,
|
|
child: AnimatedBuilder(
|
|
animation: _shimmerAnimation,
|
|
builder: (context, child) {
|
|
return ShaderMask(
|
|
shaderCallback: (rect) {
|
|
return LinearGradient(
|
|
begin: Alignment.centerLeft,
|
|
end: Alignment.centerRight,
|
|
colors: [baseColor, highlightColor, baseColor],
|
|
stops: const [0.1, 0.3, 0.6],
|
|
transform: _SlidingGradientTransform(
|
|
slidePercent: _shimmerAnimation.value,
|
|
),
|
|
).createShader(rect);
|
|
},
|
|
blendMode: BlendMode.srcATop,
|
|
child: _buildSkeletonContent(context),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSkeletonContent(BuildContext context) {
|
|
switch (widget.skeletonType) {
|
|
case SkeletonType.feed:
|
|
return _buildFeedSkeleton(context);
|
|
case SkeletonType.reels:
|
|
return _buildReelsSkeleton(context);
|
|
case SkeletonType.explore:
|
|
return _buildExploreSkeleton(context);
|
|
case SkeletonType.messages:
|
|
return _buildMessagesSkeleton(context);
|
|
case SkeletonType.profile:
|
|
return _buildProfileSkeleton(context);
|
|
case SkeletonType.generic:
|
|
return _buildGenericSkeleton(context);
|
|
}
|
|
}
|
|
|
|
Widget _buildFeedSkeleton(BuildContext context) {
|
|
final width = MediaQuery.of(context).size.width;
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
height: 80,
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: ListView.builder(
|
|
scrollDirection: Axis.horizontal,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
itemCount: 6,
|
|
itemBuilder: (context, index) => Padding(
|
|
padding: const EdgeInsets.only(right: 12),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
width: 56,
|
|
height: 56,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(28),
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Container(
|
|
width: 32,
|
|
height: 8,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: ListView.builder(
|
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
|
itemCount: 3,
|
|
itemBuilder: (context, index) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 8,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 32,
|
|
height: 32,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Container(
|
|
width: 80,
|
|
height: 12,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Container(
|
|
width: double.infinity,
|
|
height: width,
|
|
color: Colors.white,
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Row(
|
|
children: List.generate(
|
|
3,
|
|
(i) => Padding(
|
|
padding: const EdgeInsets.only(right: 16),
|
|
child: Container(
|
|
width: 24,
|
|
height: 24,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
child: Container(
|
|
width: width * 0.7,
|
|
height: 10,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildReelsSkeleton(BuildContext context) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
width: 120,
|
|
height: 200,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
Container(
|
|
width: 150,
|
|
height: 16,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
width: 100,
|
|
height: 12,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildExploreSkeleton(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.all(2),
|
|
child: GridView.builder(
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: 3,
|
|
crossAxisSpacing: 2,
|
|
mainAxisSpacing: 2,
|
|
),
|
|
itemCount: 15,
|
|
itemBuilder: (context, index) {
|
|
return Container(color: Colors.white);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMessagesSkeleton(BuildContext context) {
|
|
return ListView.builder(
|
|
itemCount: 8,
|
|
itemBuilder: (context, index) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 56,
|
|
height: 56,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(28),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
width: 100,
|
|
height: 14,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
Container(
|
|
width: 150,
|
|
height: 12,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildProfileSkeleton(BuildContext context) {
|
|
return SingleChildScrollView(
|
|
child: Column(
|
|
children: [
|
|
const SizedBox(height: 24),
|
|
Container(
|
|
width: 96,
|
|
height: 96,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(48),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Container(
|
|
width: 120,
|
|
height: 16,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: List.generate(
|
|
3,
|
|
(index) => Column(
|
|
children: [
|
|
Container(
|
|
width: 40,
|
|
height: 20,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Container(
|
|
width: 50,
|
|
height: 10,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
GridView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: 3,
|
|
crossAxisSpacing: 2,
|
|
mainAxisSpacing: 2,
|
|
),
|
|
itemCount: 9,
|
|
itemBuilder: (context, index) {
|
|
return Container(color: Colors.white);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildGenericSkeleton(BuildContext context) {
|
|
final width = MediaQuery.of(context).size.width;
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const SizedBox(height: 16),
|
|
SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
child: Row(
|
|
children: List.generate(
|
|
6,
|
|
(index) => Padding(
|
|
padding: const EdgeInsets.only(right: 12),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
width: 60,
|
|
height: 60,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(30),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
width: 40,
|
|
height: 8,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Expanded(
|
|
child: ListView.builder(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
itemCount: 3,
|
|
itemBuilder: (context, index) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(18),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
width: width * 0.4,
|
|
height: 10,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
Container(
|
|
width: width * 0.25,
|
|
height: 8,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
width: double.infinity,
|
|
height: width * 1.1,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(18),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
width: width * 0.7,
|
|
height: 10,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
Container(
|
|
width: width * 0.5,
|
|
height: 8,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SlidingGradientTransform extends GradientTransform {
|
|
final double slidePercent;
|
|
|
|
const _SlidingGradientTransform({required this.slidePercent});
|
|
|
|
@override
|
|
Matrix4 transform(Rect bounds, {TextDirection? textDirection}) {
|
|
return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0);
|
|
}
|
|
}
|
|
|
|
SkeletonType getSkeletonTypeFromUrl(String url) {
|
|
final parsed = Uri.tryParse(url);
|
|
if (parsed == null) return SkeletonType.generic;
|
|
final path = parsed.path.toLowerCase();
|
|
|
|
if (path.startsWith('/reels') || path.startsWith('/reel/')) {
|
|
return SkeletonType.reels;
|
|
} else if (path.startsWith('/explore')) {
|
|
return SkeletonType.explore;
|
|
} else if (path.startsWith('/messages') || path.startsWith('/inbox')) {
|
|
return SkeletonType.messages;
|
|
} else if (path.startsWith('/') && !path.startsWith('/accounts')) {
|
|
if (path.split('/').length <= 2) {
|
|
return SkeletonType.feed;
|
|
}
|
|
return SkeletonType.profile;
|
|
}
|
|
return SkeletonType.generic;
|
|
}
|