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: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 |
||||
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 |
||||
void initState() { |
||||
super.initState(); |
||||
// Enable hybrid composition. |
||||
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView(); |
||||
_loadMessages(); |
||||
} |
||||
|
||||
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 |
||||
Widget build(BuildContext context) { |
||||
return WebView( |
||||
initialUrl: 'https://kiwiirc.com/client/chat.freenode.net/%23ozgurkon', |
||||
return Scaffold( |
||||
body: Chat( |
||||
messages: _messages, |
||||
onPreviewDataFetched: _handlePreviewDataFetched, |
||||
onSendPressed: _handleSendPressed, |
||||
user: _user, |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
@ -1,28 +1,31 @@ |
||||
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 |
||||
Widget build(BuildContext context) { |
||||
return FutureBuilder<Map<String, VideoSource>>( |
||||
future: VideoSource.fromM3u8PlaylistUrl( |
||||
"https://sfux-ext.sfux.info/hls/chapter/105/1588724110/1588724110.m3u8", |
||||
formatter: (quality) => |
||||
quality == "Auto" ? "Automatic" : "${quality.split("x").last}p", |
||||
return Container( |
||||
width: double.infinity, |
||||
height: double.infinity, |
||||
decoration: BoxDecoration( |
||||
color: Color(0xFFededed), |
||||
image: DecorationImage( |
||||
image: Svg( |
||||
'assets/flat-mountains.svg', |
||||
), |
||||
alignment: Alignment.bottomCenter, |
||||
), |
||||
), |
||||
child: Row( |
||||
mainAxisAlignment: MainAxisAlignment.center, |
||||
children: [ |
||||
Text( |
||||
'next_year'.tr, |
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700), |
||||
) |
||||
], |
||||
), |
||||
builder: (_, data) { |
||||
return data.hasData |
||||
? VideoViewer( |
||||
source: data.data, |
||||
onFullscreenFixLandscape: true, |
||||
style: VideoViewerStyle( |
||||
thumbnail: Image.network( |
||||
"https://play-lh.googleusercontent.com/aA2iky4PH0REWCcPs9Qym2X7e9koaa1RtY-nKkXQsDVU6Ph25_9GkvVuyhS72bwKhN1P", |
||||
), |
||||
), |
||||
) |
||||
: 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'; |
||||
|
||||
class PlaceholderWidget extends StatelessWidget { |
||||
final Color color; |
||||
import 'dart:async'; |
||||
import 'dart:convert'; |
||||
|
||||
PlaceholderWidget(this.color); |
||||
import 'package:http/http.dart' as http; |
||||
import 'package:get/get.dart'; |
||||
|
||||
class Schedule extends StatelessWidget { |
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Container( |
||||
color: color, |
||||
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( |
||||
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