forked from oyd/ozgurkon-app
parent
d47ba1fe92
commit
9dc42635d0
@ -0,0 +1,38 @@ |
|||||||
|
import 'package:get/get.dart'; |
||||||
|
|
||||||
|
class Messages extends Translations { |
||||||
|
@override |
||||||
|
Map<String, Map<String, String>> get keys => { |
||||||
|
'en_US': { |
||||||
|
'chat': 'Chat', |
||||||
|
'live': 'Live', |
||||||
|
'schedule': 'Schedule', |
||||||
|
'videos': 'Videos', |
||||||
|
'about_app': 'About ÖzgürKon app', |
||||||
|
'app_description': 'Android app for ÖzgürKon, Developed in Flutter.', |
||||||
|
'view_readme': 'View README', |
||||||
|
'view_changelog': 'View changelog', |
||||||
|
'view_license': 'View license', |
||||||
|
'contributors': 'Contributors', |
||||||
|
'fs_licenses': 'Free software licenses', |
||||||
|
'loading': "Loading...", |
||||||
|
'next_year': "See you next year!", |
||||||
|
}, |
||||||
|
'tr_TR': { |
||||||
|
'chat': 'Sohbet', |
||||||
|
'live': 'Canlı', |
||||||
|
'schedule': 'Program', |
||||||
|
'videos': 'Videolar', |
||||||
|
'about_app': 'ÖzgürKon uygulaması hakkında', |
||||||
|
'app_description': |
||||||
|
'ÖzgürKon için Android uygulaması, Flutter ile geliştirildi.', |
||||||
|
'view_readme': "README'yi görüntüle", |
||||||
|
'view_changelog': "Değişiklik özetini görüntüle", |
||||||
|
'view_license': 'Lisans', |
||||||
|
'contributors': 'Katkıda bulunanlar', |
||||||
|
'fs_licenses': 'Özgür yazılım lisansları', |
||||||
|
'loading': 'Yükleniyor...', |
||||||
|
'next_year': "Seneye görüşmek üzere!", |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
@ -1,24 +1,78 @@ |
|||||||
import 'dart:io'; |
import 'dart:convert'; |
||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
import 'package:webview_flutter/webview_flutter.dart'; |
import 'package:flutter/services.dart' show rootBundle; |
||||||
|
import 'package:flutter_chat_types/flutter_chat_types.dart' as types; |
||||||
|
import 'package:flutter_chat_ui/flutter_chat_ui.dart'; |
||||||
|
import 'package:uuid/uuid.dart'; |
||||||
|
|
||||||
|
class ChatPage extends StatefulWidget { |
||||||
|
const ChatPage({Key? key}) : super(key: key); |
||||||
|
|
||||||
class WebViewExample extends StatefulWidget { |
|
||||||
@override |
@override |
||||||
WebViewExampleState createState() => WebViewExampleState(); |
_ChatPageState createState() => _ChatPageState(); |
||||||
} |
} |
||||||
|
|
||||||
class WebViewExampleState extends State<WebViewExample> { |
class _ChatPageState extends State<ChatPage> { |
||||||
|
List<types.Message> _messages = []; |
||||||
|
final _user = const types.User(id: '06c33e8b-e835-4736-80f4-63f44b66666c'); |
||||||
|
|
||||||
@override |
@override |
||||||
void initState() { |
void initState() { |
||||||
super.initState(); |
super.initState(); |
||||||
// Enable hybrid composition. |
_loadMessages(); |
||||||
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView(); |
} |
||||||
|
|
||||||
|
void _addMessage(types.Message message) { |
||||||
|
setState(() { |
||||||
|
_messages.insert(0, message); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
void _handlePreviewDataFetched( |
||||||
|
types.TextMessage message, |
||||||
|
types.PreviewData previewData, |
||||||
|
) { |
||||||
|
final index = _messages.indexWhere((element) => element.id == message.id); |
||||||
|
final updatedMessage = _messages[index].copyWith(previewData: previewData); |
||||||
|
|
||||||
|
WidgetsBinding.instance?.addPostFrameCallback((_) { |
||||||
|
setState(() { |
||||||
|
_messages[index] = updatedMessage; |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
void _handleSendPressed(types.PartialText message) { |
||||||
|
final textMessage = types.TextMessage( |
||||||
|
author: _user, |
||||||
|
createdAt: DateTime.now().millisecondsSinceEpoch, |
||||||
|
id: const Uuid().v4(), |
||||||
|
text: message.text, |
||||||
|
); |
||||||
|
|
||||||
|
_addMessage(textMessage); |
||||||
|
} |
||||||
|
|
||||||
|
void _loadMessages() async { |
||||||
|
final response = await rootBundle.loadString('assets/messages.json'); |
||||||
|
final messages = (jsonDecode(response) as List) |
||||||
|
.map((e) => types.Message.fromJson(e as Map<String, dynamic>)) |
||||||
|
.toList(); |
||||||
|
|
||||||
|
setState(() { |
||||||
|
_messages = messages; |
||||||
|
}); |
||||||
} |
} |
||||||
|
|
||||||
@override |
@override |
||||||
Widget build(BuildContext context) { |
Widget build(BuildContext context) { |
||||||
return WebView( |
return Scaffold( |
||||||
initialUrl: 'https://kiwiirc.com/client/chat.freenode.net/%23ozgurkon', |
body: Chat( |
||||||
|
messages: _messages, |
||||||
|
onPreviewDataFetched: _handlePreviewDataFetched, |
||||||
|
onSendPressed: _handleSendPressed, |
||||||
|
user: _user, |
||||||
|
), |
||||||
); |
); |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,28 +1,31 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
import 'package:video_viewer/video_viewer.dart'; |
import 'package:get/get.dart'; |
||||||
|
import 'package:flutter_svg_provider/flutter_svg_provider.dart'; |
||||||
|
|
||||||
class HLSVideoExample extends StatelessWidget { |
class Live extends StatelessWidget { |
||||||
@override |
@override |
||||||
Widget build(BuildContext context) { |
Widget build(BuildContext context) { |
||||||
return FutureBuilder<Map<String, VideoSource>>( |
return Container( |
||||||
future: VideoSource.fromM3u8PlaylistUrl( |
width: double.infinity, |
||||||
"https://sfux-ext.sfux.info/hls/chapter/105/1588724110/1588724110.m3u8", |
height: double.infinity, |
||||||
formatter: (quality) => |
decoration: BoxDecoration( |
||||||
quality == "Auto" ? "Automatic" : "${quality.split("x").last}p", |
color: Color(0xFFededed), |
||||||
|
image: DecorationImage( |
||||||
|
image: Svg( |
||||||
|
'assets/flat-mountains.svg', |
||||||
), |
), |
||||||
builder: (_, data) { |
alignment: Alignment.bottomCenter, |
||||||
return data.hasData |
|
||||||
? VideoViewer( |
|
||||||
source: data.data, |
|
||||||
onFullscreenFixLandscape: true, |
|
||||||
style: VideoViewerStyle( |
|
||||||
thumbnail: Image.network( |
|
||||||
"https://play-lh.googleusercontent.com/aA2iky4PH0REWCcPs9Qym2X7e9koaa1RtY-nKkXQsDVU6Ph25_9GkvVuyhS72bwKhN1P", |
|
||||||
), |
), |
||||||
), |
), |
||||||
|
child: Row( |
||||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||||
|
children: [ |
||||||
|
Text( |
||||||
|
'next_year'.tr, |
||||||
|
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700), |
||||||
) |
) |
||||||
: CircularProgressIndicator(); |
], |
||||||
}, |
), |
||||||
); |
); |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,11 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:ozgurkon_app/screens/About.dart'; |
||||||
|
|
||||||
|
class Settings extends StatelessWidget { |
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return Scaffold( |
||||||
|
body: MyAbout(), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,140 @@ |
|||||||
|
import 'dart:async'; |
||||||
|
import 'dart:convert'; |
||||||
|
|
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:http/http.dart' as http; |
||||||
|
import 'package:get/get.dart'; |
||||||
|
import 'package:fluttericon/entypo_icons.dart'; |
||||||
|
import 'package:flutter/cupertino.dart'; |
||||||
|
import 'package:better_player/better_player.dart'; |
||||||
|
|
||||||
|
class Video { |
||||||
|
final String uuid; |
||||||
|
final String name; |
||||||
|
final String desc; |
||||||
|
final String speaker; |
||||||
|
final String lang; |
||||||
|
final int duration; |
||||||
|
final String streamURL; |
||||||
|
final String downloadURL; |
||||||
|
|
||||||
|
Video( |
||||||
|
{required this.uuid, |
||||||
|
required this.name, |
||||||
|
required this.desc, |
||||||
|
required this.speaker, |
||||||
|
required this.lang, |
||||||
|
required this.duration, |
||||||
|
required this.streamURL, |
||||||
|
required this.downloadURL}); |
||||||
|
|
||||||
|
factory Video.fromJson(Map<String, dynamic> json) { |
||||||
|
return Video( |
||||||
|
uuid: json['uuid'], |
||||||
|
name: json['name'].split(' | ')[0].split(' by ')[0], |
||||||
|
desc: json['description'], |
||||||
|
speaker: json['name'].split(' | ')[0].split(' by ')[1], |
||||||
|
lang: json['language']['id'], |
||||||
|
duration: json['duration'], |
||||||
|
streamURL: json['files'][0]['fileUrl'], |
||||||
|
downloadURL: json['files'][0]['fileDownloadUrl'], |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class VideoView extends StatelessWidget { |
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return FutureBuilder<Video>( |
||||||
|
future: _fetchVideoDetails(), |
||||||
|
builder: (context, snapshot) { |
||||||
|
if (snapshot.hasData) { |
||||||
|
Video? data = snapshot.data; |
||||||
|
return new Scaffold( |
||||||
|
appBar: AppBar( |
||||||
|
title: Text("Video"), |
||||||
|
), |
||||||
|
body: new SingleChildScrollView( |
||||||
|
child: Column( |
||||||
|
mainAxisAlignment: MainAxisAlignment.start, |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
children: <Widget>[_videoView(data)], |
||||||
|
), |
||||||
|
)); |
||||||
|
} else if (snapshot.hasError) { |
||||||
|
return Text("${snapshot.error}"); |
||||||
|
} |
||||||
|
return Container(child: Row(children: [CircularProgressIndicator()])); |
||||||
|
}, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Future<Video> _fetchVideoDetails() async { |
||||||
|
String uuid = Get.parameters['uuid'] as String; |
||||||
|
final String videoDetailAPI = |
||||||
|
'https://video.ozgurkon.org/api/v1/videos/' + uuid; |
||||||
|
final response = await http.get(Uri.parse(videoDetailAPI)); |
||||||
|
|
||||||
|
if (response.statusCode == 200) { |
||||||
|
var jsonResponse = json.decode(response.body); |
||||||
|
final description = |
||||||
|
await http.get(Uri.parse(videoDetailAPI + '/description')); |
||||||
|
if (description.statusCode == 200) { |
||||||
|
var descriptionResponse = json.decode(description.body); |
||||||
|
jsonResponse['description'] = descriptionResponse['description']; |
||||||
|
} |
||||||
|
return Video.fromJson(jsonResponse); |
||||||
|
} else { |
||||||
|
throw Exception('Failed to load video details from API'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Card _videoView(data) { |
||||||
|
return Card( |
||||||
|
child: Column( |
||||||
|
mainAxisSize: MainAxisSize.min, |
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||||
|
children: <Widget>[ |
||||||
|
AspectRatio( |
||||||
|
aspectRatio: 16 / 9, |
||||||
|
child: BetterPlayer.network( |
||||||
|
data!.streamURL, |
||||||
|
betterPlayerConfiguration: BetterPlayerConfiguration( |
||||||
|
aspectRatio: 16 / 9, |
||||||
|
autoPlay: true, |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
ListTile( |
||||||
|
title: Text(data.name), |
||||||
|
subtitle: Text(data.speaker), |
||||||
|
trailing: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[ |
||||||
|
Icon(Entypo.cc), |
||||||
|
Icon(Entypo.cc_by), |
||||||
|
Icon(Entypo.cc_sa) |
||||||
|
]), |
||||||
|
), |
||||||
|
Padding( |
||||||
|
padding: const EdgeInsets.all(16.0), |
||||||
|
child: Text( |
||||||
|
data.desc, |
||||||
|
style: TextStyle(color: Colors.black.withOpacity(0.6)), |
||||||
|
), |
||||||
|
), |
||||||
|
Row( |
||||||
|
mainAxisAlignment: MainAxisAlignment.end, |
||||||
|
children: <Widget>[ |
||||||
|
TextButton( |
||||||
|
child: const Text('DOWNLOAD'), |
||||||
|
onPressed: () { |
||||||
|
Get.toNamed('/videoplayer', arguments: data.streamURL); |
||||||
|
}, |
||||||
|
), |
||||||
|
const SizedBox(width: 8), |
||||||
|
], |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,93 @@ |
|||||||
|
import 'dart:async'; |
||||||
|
import 'dart:convert'; |
||||||
|
|
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:http/http.dart' as http; |
||||||
|
import 'package:get/get.dart'; |
||||||
|
|
||||||
|
class VideoList { |
||||||
|
final String uuid; |
||||||
|
final String name; |
||||||
|
final String speaker; |
||||||
|
final String lang; |
||||||
|
final String thumbnail; |
||||||
|
|
||||||
|
VideoList( |
||||||
|
{required this.uuid, |
||||||
|
required this.name, |
||||||
|
required this.speaker, |
||||||
|
required this.lang, |
||||||
|
required this.thumbnail}); |
||||||
|
|
||||||
|
factory VideoList.fromJson(Map<String, dynamic> json) { |
||||||
|
return VideoList( |
||||||
|
uuid: json['uuid'], |
||||||
|
name: json['name'].split(' | ')[0].split(' by ')[0], |
||||||
|
speaker: json['name'].split(' | ')[0].split(' by ')[1], |
||||||
|
lang: json['language']['id'], |
||||||
|
thumbnail: json['thumbnailPath'], |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class VideosListView extends StatelessWidget { |
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return FutureBuilder<List<VideoList>>( |
||||||
|
future: _fetchVideos(), |
||||||
|
builder: (context, snapshot) { |
||||||
|
if (snapshot.hasData) { |
||||||
|
List<VideoList>? data = snapshot.data; |
||||||
|
return _videosListView(data); |
||||||
|
} else if (snapshot.hasError) { |
||||||
|
return Text("${snapshot.error}"); |
||||||
|
} |
||||||
|
return Container( |
||||||
|
child: Row( |
||||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||||
|
crossAxisAlignment: CrossAxisAlignment.center, |
||||||
|
children: [CircularProgressIndicator()])); |
||||||
|
}, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Future<List<VideoList>> _fetchVideos() async { |
||||||
|
final videosFrom2020API = |
||||||
|
'https://video.ozgurkon.org/api/v1/video-channels/ozgurkon2020/videos'; |
||||||
|
final response = await http.get(Uri.parse(videosFrom2020API)); |
||||||
|
|
||||||
|
if (response.statusCode == 200) { |
||||||
|
Map<String, dynamic> jsonResponse = json.decode(response.body); |
||||||
|
List videoList = jsonResponse['data']; |
||||||
|
return videoList.map((job) => new VideoList.fromJson(job)).toList(); |
||||||
|
} else { |
||||||
|
throw Exception('Failed to load videos from API'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ListView _videosListView(data) { |
||||||
|
return ListView.builder( |
||||||
|
itemCount: data.length, |
||||||
|
itemBuilder: (context, index) { |
||||||
|
return _tile(data[index].uuid, data[index].name, data[index].speaker, |
||||||
|
data[index].thumbnail, data[index].lang); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
ListTile _tile(String uuid, String title, String subtitle, String thumbnail, |
||||||
|
String lang) => |
||||||
|
ListTile( |
||||||
|
title: Text(title, |
||||||
|
style: TextStyle( |
||||||
|
fontWeight: FontWeight.w500, |
||||||
|
fontSize: 20, |
||||||
|
)), |
||||||
|
subtitle: Text(subtitle), |
||||||
|
leading: Image.network('https://video.ozgurkon.org/' + thumbnail), |
||||||
|
trailing: lang == "tr" ? Text("🇹🇷") : Text('🇬🇧'), |
||||||
|
onTap: () { |
||||||
|
print(uuid); |
||||||
|
Get.toNamed('/video/$uuid'); |
||||||
|
}, |
||||||
|
); |
||||||
|
} |
@ -1,14 +1,125 @@ |
|||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
class PlaceholderWidget extends StatelessWidget { |
import 'dart:async'; |
||||||
final Color color; |
import 'dart:convert'; |
||||||
|
|
||||||
PlaceholderWidget(this.color); |
import 'package:http/http.dart' as http; |
||||||
|
import 'package:get/get.dart'; |
||||||
|
|
||||||
|
class Schedule extends StatelessWidget { |
||||||
@override |
@override |
||||||
Widget build(BuildContext context) { |
Widget build(BuildContext context) { |
||||||
|
return DefaultTabController( |
||||||
|
length: 2, |
||||||
|
child: Scaffold( |
||||||
|
appBar: AppBar( |
||||||
|
flexibleSpace: new Column( |
||||||
|
mainAxisAlignment: MainAxisAlignment.end, |
||||||
|
children: [ |
||||||
|
new TabBar( |
||||||
|
tabs: [ |
||||||
|
new Tab( |
||||||
|
text: "2021-05-29", |
||||||
|
), |
||||||
|
new Tab( |
||||||
|
text: "2021-05-30", |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
body: TabBarView( |
||||||
|
children: [ |
||||||
|
Icon(Icons.directions_car), |
||||||
|
Icon(Icons.directions_transit), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ScheduleItem { |
||||||
|
final String time; |
||||||
|
final String name; |
||||||
|
final List speakers; |
||||||
|
final String lang; |
||||||
|
|
||||||
|
ScheduleItem( |
||||||
|
{required this.time, |
||||||
|
required this.name, |
||||||
|
required this.speakers, |
||||||
|
required this.lang}); |
||||||
|
|
||||||
|
factory ScheduleItem.fromJson(Map<String, dynamic> json) { |
||||||
|
return ScheduleItem( |
||||||
|
time: json['time'], |
||||||
|
name: json['name'], |
||||||
|
speakers: json['speakers'], |
||||||
|
lang: json['lang'], |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ScheduleView extends StatelessWidget { |
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return FutureBuilder<List<ScheduleItem>>( |
||||||
|
future: _fetchSchedule(), |
||||||
|
builder: (context, snapshot) { |
||||||
|
if (snapshot.hasData) { |
||||||
|
List<ScheduleItem>? data = snapshot.data; |
||||||
|
return _scheduleListView(data); |
||||||
|
} else if (snapshot.hasError) { |
||||||
|
return Text("${snapshot.error}"); |
||||||
|
} |
||||||
return Container( |
return Container( |
||||||
color: color, |
child: Row( |
||||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||||
|
crossAxisAlignment: CrossAxisAlignment.center, |
||||||
|
children: [CircularProgressIndicator()])); |
||||||
|
}, |
||||||
); |
); |
||||||
} |
} |
||||||
|
|
||||||
|
Future<List<ScheduleItem>> _fetchSchedule() async { |
||||||
|
final videosFrom2020API = |
||||||
|
'https://video.ozgurkon.org/api/v1/video-channels/ozgurkon2020/videos'; |
||||||
|
final response = await http.get(Uri.parse(videosFrom2020API)); |
||||||
|
|
||||||
|
if (response.statusCode == 200) { |
||||||
|
Map<String, dynamic> jsonResponse = json.decode(response.body); |
||||||
|
List scheduleList = jsonResponse['data']; |
||||||
|
return scheduleList.map((job) => new ScheduleItem.fromJson(job)).toList(); |
||||||
|
} else { |
||||||
|
throw Exception('Failed to load schedule from API'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ListView _scheduleListView(data) { |
||||||
|
return ListView.builder( |
||||||
|
itemCount: data.length, |
||||||
|
itemBuilder: (context, index) { |
||||||
|
return _tile(data[index].uuid, data[index].name, data[index].speaker, |
||||||
|
data[index].thumbnail, data[index].lang); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
ListTile _tile(String uuid, String title, String subtitle, String thumbnail, |
||||||
|
String lang) => |
||||||
|
ListTile( |
||||||
|
title: Text(title, |
||||||
|
style: TextStyle( |
||||||
|
fontWeight: FontWeight.w500, |
||||||
|
fontSize: 20, |
||||||
|
)), |
||||||
|
subtitle: Text(subtitle), |
||||||
|
leading: Image.network('https://video.ozgurkon.org/' + thumbnail), |
||||||
|
trailing: lang == "tr" ? Text("🇹🇷") : Text('🇬🇧'), |
||||||
|
onTap: () { |
||||||
|
print(uuid); |
||||||
|
Get.toNamed('/video/$uuid'); |
||||||
|
}, |
||||||
|
); |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue