Skip to content

Instantly share code, notes, and snippets.

@rubywai
Created March 1, 2025 14:20
Show Gist options
  • Save rubywai/dfd13c6b3ef03111960b8e976f4fb59e to your computer and use it in GitHub Desktop.
Save rubywai/dfd13c6b3ef03111960b8e976f4fb59e to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
// Set preferred orientations and system UI
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
),
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'TikTok Style Image Viewer',
theme: ThemeData.dark(),
debugShowCheckedModeBanner: false,
home: const TikTokStyleFeed(),
);
}
}
class TikTokStyleFeed extends StatefulWidget {
const TikTokStyleFeed({super.key});
@override
State<TikTokStyleFeed> createState() => _TikTokStyleFeedState();
}
class _TikTokStyleFeedState extends State<TikTokStyleFeed> {
final PageController _pageController = PageController();
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: const Text(
'For You',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
centerTitle: true,
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () {
// Implement search functionality
},
),
],
),
body: PageView.builder(
scrollDirection: Axis.vertical,
controller: _pageController,
itemCount: 100,
onPageChanged: (int page) {
setState(() {});
},
itemBuilder: (context, index) {
return TikTokStylePost(
imageUrl: 'https://picsum.photos/200/300?random=$index',
index: index,
);
},
),
);
}
}
class TikTokStylePost extends StatelessWidget {
final String imageUrl;
final int index;
const TikTokStylePost({
super.key,
required this.imageUrl,
required this.index,
});
@override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: [
// Background image
Image.network(
key: UniqueKey(),
imageUrl,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) {
return child;
}
return Container(
color: Colors.blue,
child: Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
(loadingProgress.expectedTotalBytes ?? 1)
: null,
color: Colors.white,
),
),
);
},
errorBuilder: (context, error, stackTrace) {
return Container(
color: Colors.black,
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, color: Colors.red, size: 48),
SizedBox(height: 16),
Text(
'Could not load image',
style: TextStyle(color: Colors.white),
),
],
),
),
);
},
),
// Dark gradient overlay for better text visibility
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withValues(alpha: 0.3),
Colors.black.withValues(alpha: 0.0),
Colors.black.withValues(alpha: 0.0),
Colors.black.withValues(alpha: 0.7),
],
stops: const [0.0, 0.2, 0.8, 1.0],
),
),
),
// User info and caption at bottom
Positioned(
left: 16,
right: 96,
bottom: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text(
'photographer',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const SizedBox(width: 8), // FIXED TYPO
Container(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.circular(10),
),
child: const Text(
'Follow',
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
],
),
const SizedBox(height: 8), // FIXED TYPO
Text(
'Beautiful landscape photo #${index + 1}',
style: const TextStyle(color: Colors.white),
),
const SizedBox(height: 8), // FIXED TYPO
Row(
children: const [
Icon(Icons.music_note, color: Colors.white, size: 16),
SizedBox(width: 4),
Text(
'original sound - photography',
style: TextStyle(color: Colors.white),
),
],
),
],
),
),
// Right-side interaction buttons
Positioned(
right: 16,
bottom: 100,
child: Column(
children: [
_buildInteractionButton(Icons.favorite, '${(index + 1) * 234}K'),
_buildInteractionButton(
Icons.chat_bubble, '${(index + 1) * 12}K'),
_buildInteractionButton(Icons.bookmark, 'Save'),
_buildInteractionButton(Icons.share, 'Share'),
],
),
),
// Watermark
Positioned(
bottom: 20,
right: 20,
child: Text(
'© RubyLearner',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.6),
fontSize: 12,
),
),
),
],
);
}
Widget _buildInteractionButton(IconData icon, String label) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Column(
children: [
Icon(icon, color: Colors.white, size: 32),
const SizedBox(height: 4),
Text(
label,
style: const TextStyle(color: Colors.white, fontSize: 12),
),
],
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment