mirror of
https://github.com/ErfanRht/MovieLab.git
synced 2025-05-17 14:05:55 +08:00
Adding new features
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:hive_flutter/adapters.dart';
|
import 'package:hive_flutter/adapters.dart';
|
||||||
@ -33,7 +34,7 @@ void main() async {
|
|||||||
Get.put(SearchBarController());
|
Get.put(SearchBarController());
|
||||||
Get.put(CacheData());
|
Get.put(CacheData());
|
||||||
|
|
||||||
runApp(const App());
|
runApp(const ProviderScope(child: App()));
|
||||||
}
|
}
|
||||||
|
|
||||||
class App extends StatelessWidget {
|
class App extends StatelessWidget {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:movielab/models/hive/models/show_preview.dart';
|
import 'package:movielab/models/hive/models/show_preview.dart';
|
||||||
import 'package:movielab/models/show_models/show_preview_model.dart';
|
import 'package:movielab/models/show_models/show_preview_model.dart';
|
||||||
|
|
||||||
@ -43,7 +44,10 @@ HiveShowPreview convertShowPreviewToHive(ShowPreview showPreview) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<HiveShowPreview> convertFullShowToHive(
|
Future<HiveShowPreview> convertFullShowToHive(
|
||||||
FullShow fullShow, String rank) async {
|
{required FullShow fullShow,
|
||||||
|
required String rank,
|
||||||
|
DateTime? date,
|
||||||
|
TimeOfDay? time}) async {
|
||||||
String crew = "";
|
String crew = "";
|
||||||
await getShowCrew(fullShow: fullShow).then((value) => crew = value);
|
await getShowCrew(fullShow: fullShow).then((value) => crew = value);
|
||||||
return HiveShowPreview()
|
return HiveShowPreview()
|
||||||
@ -61,7 +65,9 @@ Future<HiveShowPreview> convertFullShowToHive(
|
|||||||
..domestic = fullShow.domestic
|
..domestic = fullShow.domestic
|
||||||
..domesticLifetimeGross = fullShow.domesticLifetimeGross
|
..domesticLifetimeGross = fullShow.domesticLifetimeGross
|
||||||
..foreign = fullShow.foreign
|
..foreign = fullShow.foreign
|
||||||
..foreignLifetimeGross = fullShow.foreignLifetimeGross;
|
..foreignLifetimeGross = fullShow.foreignLifetimeGross
|
||||||
|
..watchDate = date
|
||||||
|
..watchTime = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getShowCrew({required FullShow fullShow}) async {
|
Future<String> getShowCrew({required FullShow fullShow}) async {
|
||||||
|
@ -14,4 +14,6 @@ class ShowPreviewFields {
|
|||||||
static const int domestic = 12;
|
static const int domestic = 12;
|
||||||
static const int foreignLifetimeGross = 13;
|
static const int foreignLifetimeGross = 13;
|
||||||
static const int foreign = 14;
|
static const int foreign = 14;
|
||||||
|
static const int watchDate = 15;
|
||||||
|
static const int watchTime = 16;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import '../hive_helper/fields/show_preview_fields.dart';
|
import '../hive_helper/fields/show_preview_fields.dart';
|
||||||
import '../hive_helper/hive_adapters.dart';
|
import '../hive_helper/hive_adapters.dart';
|
||||||
@ -37,4 +38,8 @@ class HiveShowPreview extends HiveObject {
|
|||||||
late String foreignLifetimeGross;
|
late String foreignLifetimeGross;
|
||||||
@HiveField(ShowPreviewFields.foreign)
|
@HiveField(ShowPreviewFields.foreign)
|
||||||
late String foreign;
|
late String foreign;
|
||||||
|
@HiveField(ShowPreviewFields.watchDate)
|
||||||
|
late DateTime? watchDate;
|
||||||
|
@HiveField(ShowPreviewFields.watchTime)
|
||||||
|
late TimeOfDay? watchTime;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
// Movie or TV show preview model class
|
// Movie or TV show preview model class
|
||||||
class ShowPreview {
|
class ShowPreview {
|
||||||
final String id;
|
final String id;
|
||||||
@ -17,6 +19,8 @@ class ShowPreview {
|
|||||||
final String domestic;
|
final String domestic;
|
||||||
final String foreignLifetimeGross;
|
final String foreignLifetimeGross;
|
||||||
final String foreign;
|
final String foreign;
|
||||||
|
final DateTime? watchDate;
|
||||||
|
final TimeOfDay? watchTime;
|
||||||
|
|
||||||
const ShowPreview({
|
const ShowPreview({
|
||||||
required this.id,
|
required this.id,
|
||||||
@ -34,6 +38,8 @@ class ShowPreview {
|
|||||||
required this.domestic,
|
required this.domestic,
|
||||||
required this.foreignLifetimeGross,
|
required this.foreignLifetimeGross,
|
||||||
required this.foreign,
|
required this.foreign,
|
||||||
|
this.watchDate,
|
||||||
|
this.watchTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ShowPreview.fromJson(Map<String, dynamic> json) {
|
factory ShowPreview.fromJson(Map<String, dynamic> json) {
|
||||||
|
5
lib/modules/capitalizer.dart
Normal file
5
lib/modules/capitalizer.dart
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
extension StringExtension on String {
|
||||||
|
String capitalize() {
|
||||||
|
return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive_flutter/adapters.dart';
|
import 'package:hive_flutter/adapters.dart';
|
||||||
import '../models/hive/convertor.dart';
|
import '../models/hive/convertor.dart';
|
||||||
import '../models/hive/models/show_preview.dart';
|
import '../models/hive/models/show_preview.dart';
|
||||||
@ -16,10 +17,16 @@ class PreferencesShareholder {
|
|||||||
|
|
||||||
// Add an item to a list in the shared preferences
|
// Add an item to a list in the shared preferences
|
||||||
Future<bool> addShowToList(
|
Future<bool> addShowToList(
|
||||||
{required FullShow fullShow, required String listName}) async {
|
{required FullShow fullShow,
|
||||||
|
required String listName,
|
||||||
|
DateTime? date,
|
||||||
|
TimeOfDay? time}) async {
|
||||||
Box<HiveShowPreview> list = Hive.box<HiveShowPreview>(listName);
|
Box<HiveShowPreview> list = Hive.box<HiveShowPreview>(listName);
|
||||||
HiveShowPreview hiveShow =
|
HiveShowPreview hiveShow = await convertFullShowToHive(
|
||||||
await convertFullShowToHive(fullShow, (list.length + 1).toString());
|
fullShow: fullShow,
|
||||||
|
rank: (list.length + 1).toString(),
|
||||||
|
date: date,
|
||||||
|
time: time);
|
||||||
list.put(list.length + 1, hiveShow);
|
list.put(list.length + 1, hiveShow);
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print("The item added to $listName");
|
print("The item added to $listName");
|
||||||
@ -48,17 +55,21 @@ class PreferencesShareholder {
|
|||||||
List<String> listNames = ["collection", "watchlist", "history"];
|
List<String> listNames = ["collection", "watchlist", "history"];
|
||||||
Map<String, bool> result = {};
|
Map<String, bool> result = {};
|
||||||
for (String listName in listNames) {
|
for (String listName in listNames) {
|
||||||
Box<HiveShowPreview> collection = Hive.box<HiveShowPreview>(listName);
|
Box<HiveShowPreview> list = Hive.box<HiveShowPreview>(listName);
|
||||||
for (int i = 0; i < collection.length; i++) {
|
for (int i = 0; i < list.length; i++) {
|
||||||
if (collection.getAt(i)?.id == showId) {
|
if (list.getAt(i)?.id == showId) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print("Item is in $listName");
|
print("Item is in $listName");
|
||||||
}
|
}
|
||||||
result[listName] = true;
|
result[listName] = true;
|
||||||
break;
|
break;
|
||||||
|
} else {
|
||||||
|
result[listName] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result[listName] = false;
|
if (result[listName] != true) {
|
||||||
|
result[listName] = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,40 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import '../../../../../constants/colors.dart';
|
import '../../../../../constants/colors.dart';
|
||||||
|
import '../../../../../models/show_models/full_show_model.dart';
|
||||||
|
import '../../../../../modules/preferences_shareholder.dart';
|
||||||
|
import 'sections/lists_info/lists_info.dart';
|
||||||
|
|
||||||
class ShowPageBottonBar extends StatelessWidget {
|
class ShowPageBottonBar extends StatefulWidget {
|
||||||
const ShowPageBottonBar({Key? key}) : super(key: key);
|
final FullShow show;
|
||||||
|
final Map<String, bool> isThereInLists;
|
||||||
|
const ShowPageBottonBar(
|
||||||
|
{Key? key, required this.show, required this.isThereInLists})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ShowPageBottonBar> createState() => _ShowPageBottonBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShowPageBottonBarState extends State<ShowPageBottonBar>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
final PreferencesShareholder _preferencesShareholder =
|
||||||
|
PreferencesShareholder();
|
||||||
|
late FToast fToast;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
fToast = FToast();
|
||||||
|
fToast.init(context);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(widget.isThereInLists);
|
||||||
|
}
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 60,
|
height: 60,
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -34,23 +62,12 @@ class ShowPageBottonBar extends StatelessWidget {
|
|||||||
)),
|
)),
|
||||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
backgroundColor: kSecondaryColor,
|
backgroundColor: kSecondaryColor,
|
||||||
|
transitionAnimationController: AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 235), vsync: this),
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return Container(
|
return ShowPageListsInfo(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
show: widget.show,
|
||||||
height: 235,
|
isThereInLists: widget.isThereInLists,
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
button(context,
|
|
||||||
icon: FontAwesomeIcons.circle,
|
|
||||||
text: 'Mark as watched'),
|
|
||||||
button(context,
|
|
||||||
icon: FontAwesomeIcons.bookmark,
|
|
||||||
text: 'Add to watchlist'),
|
|
||||||
button(context,
|
|
||||||
icon: FontAwesomeIcons.rectangleList,
|
|
||||||
text: 'Add to collection'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -108,44 +125,4 @@ class ShowPageBottonBar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget button(BuildContext context,
|
|
||||||
{required String text,
|
|
||||||
required IconData icon,
|
|
||||||
EdgeInsets margin = const EdgeInsets.symmetric(vertical: 7.5)}) {
|
|
||||||
return Container(
|
|
||||||
height: 50,
|
|
||||||
margin: margin,
|
|
||||||
width: MediaQuery.of(context).size.width - 100,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xff2a425f).withOpacity(0.75),
|
|
||||||
borderRadius: BorderRadius.circular(15)),
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () {},
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(15),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 30),
|
|
||||||
child: Icon(
|
|
||||||
icon,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
text,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 15,
|
|
||||||
fontWeight: FontWeight.w800),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,142 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:movielab/modules/capitalizer.dart';
|
||||||
|
import '../../../../../../../constants/colors.dart';
|
||||||
|
import '../../../../../../../models/show_models/full_show_model.dart';
|
||||||
|
import '../../../../../../../modules/preferences_shareholder.dart';
|
||||||
|
import '../../../../../../../widgets/buttons/activeable_button.dart';
|
||||||
|
import '../../../../../../../widgets/toast.dart';
|
||||||
|
import '../../watchtime.dart';
|
||||||
|
|
||||||
|
class ShowPageListsInfo extends StatefulWidget {
|
||||||
|
final FullShow show;
|
||||||
|
final Map<String, bool> isThereInLists;
|
||||||
|
const ShowPageListsInfo(
|
||||||
|
{Key? key, required this.show, required this.isThereInLists})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ShowPageListsInfo> createState() => _ShowPageListsInfoState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShowPageListsInfoState extends State<ShowPageListsInfo>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
final PreferencesShareholder _preferencesShareholder =
|
||||||
|
PreferencesShareholder();
|
||||||
|
late FToast fToast;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
fToast = FToast();
|
||||||
|
fToast.init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||||
|
height: 235,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ActiveableButton(
|
||||||
|
icon: FontAwesomeIcons.circle,
|
||||||
|
activeIcon: FontAwesomeIcons.solidCircle,
|
||||||
|
text: 'Mark as watched',
|
||||||
|
activeText: 'Watched',
|
||||||
|
activeColor: kPrimaryColor,
|
||||||
|
isActive: widget.isThereInLists['history'] ?? false,
|
||||||
|
onTap: () async {
|
||||||
|
if (widget.isThereInLists["history"] == false) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
top: Radius.circular(20),
|
||||||
|
)),
|
||||||
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
|
backgroundColor: kSecondaryColor,
|
||||||
|
transitionAnimationController: AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 225),
|
||||||
|
vsync: this),
|
||||||
|
builder: (context) {
|
||||||
|
return ShowPageAddWatchDate(
|
||||||
|
show: widget.show,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleOnTap(listName: "history");
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
ActiveableButton(
|
||||||
|
icon: FontAwesomeIcons.bookmark,
|
||||||
|
activeIcon: FontAwesomeIcons.solidBookmark,
|
||||||
|
text: 'Add to watchlist',
|
||||||
|
activeText: 'Listed on watchlist',
|
||||||
|
activeColor: kAccentColor,
|
||||||
|
isActive: widget.isThereInLists['watchlist'] ?? false,
|
||||||
|
onTap: () {
|
||||||
|
handleOnTap(listName: "watchlist");
|
||||||
|
}),
|
||||||
|
ActiveableButton(
|
||||||
|
icon: FontAwesomeIcons.rectangleList,
|
||||||
|
activeIcon: FontAwesomeIcons.bookBookmark,
|
||||||
|
text: 'Add to collection',
|
||||||
|
activeText: 'Collected',
|
||||||
|
activeColor: kImdbColor,
|
||||||
|
isActive: widget.isThereInLists['collection'] ?? false,
|
||||||
|
onTap: () {
|
||||||
|
handleOnTap(listName: "collection");
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleOnTap({
|
||||||
|
required String listName,
|
||||||
|
}) async {
|
||||||
|
if (widget.isThereInLists[listName] == false) {
|
||||||
|
_preferencesShareholder.addShowToList(
|
||||||
|
fullShow: widget.show, listName: listName);
|
||||||
|
setState(() {
|
||||||
|
widget.isThereInLists[listName] = true;
|
||||||
|
});
|
||||||
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
Navigator.pop(context);
|
||||||
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
|
fToast.showToast(
|
||||||
|
child: ToastWidget(
|
||||||
|
mainText: "Saved to ${listName.capitalize()}",
|
||||||
|
buttonText: "See list",
|
||||||
|
buttonColor: kAccentColor,
|
||||||
|
buttonOnTap: () {},
|
||||||
|
),
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
toastDuration: const Duration(seconds: 3),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_preferencesShareholder.deleteFromList(
|
||||||
|
showId: widget.show.id, listName: listName);
|
||||||
|
setState(() {
|
||||||
|
widget.isThereInLists[listName] = false;
|
||||||
|
});
|
||||||
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
Navigator.pop(context);
|
||||||
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
|
fToast.showToast(
|
||||||
|
child: ToastWidget(
|
||||||
|
mainText: "Ramoved from ${listName.capitalize()}",
|
||||||
|
buttonText: "Undo",
|
||||||
|
buttonColor: kPrimaryColor,
|
||||||
|
buttonOnTap: () {}),
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
toastDuration: const Duration(seconds: 3),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,101 +1,57 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:movielab/constants/colors.dart';
|
import 'package:movielab/constants/colors.dart';
|
||||||
import 'package:movielab/models/show_models/full_show_model.dart';
|
import 'package:movielab/models/show_models/full_show_model.dart';
|
||||||
import 'package:movielab/widgets/buttons/glassmorphism_button.dart';
|
import 'package:movielab/widgets/buttons/glassmorphism_button.dart';
|
||||||
|
import '../../../../../modules/preferences_shareholder.dart';
|
||||||
|
import '../../../../../widgets/toast.dart';
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
class ShowPageAddWatchDate extends ConsumerStatefulWidget {
|
||||||
class ShowPageAddWatchTime extends StatefulWidget {
|
|
||||||
final FullShow show;
|
final FullShow show;
|
||||||
const ShowPageAddWatchTime({Key? key, required this.show}) : super(key: key);
|
const ShowPageAddWatchDate({Key? key, required this.show}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ShowPageAddWatchTime> createState() => _ShowPageAddWatchTimeState();
|
ShowPageAddWatchDateState createState() => ShowPageAddWatchDateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ShowPageAddWatchTimeState extends State<ShowPageAddWatchTime> {
|
class ShowPageAddWatchDateState extends ConsumerState<ShowPageAddWatchDate> {
|
||||||
bool isOtherDateSectionOpen = false;
|
final PreferencesShareholder _preferencesShareholder =
|
||||||
bool showDateSelector = false;
|
PreferencesShareholder();
|
||||||
DateTime selectedDate = DateTime.now();
|
late bool isOtherDateSectionOpen, showDateSelector;
|
||||||
TimeOfDay selectedTime = TimeOfDay.now();
|
late TimeOfDay selectedTime;
|
||||||
List<String> months = [
|
late DateTime selectedDate;
|
||||||
'January',
|
late List<String> months;
|
||||||
'February',
|
late FToast fToast;
|
||||||
'March',
|
|
||||||
'April',
|
|
||||||
'May',
|
|
||||||
'June',
|
|
||||||
'July',
|
|
||||||
'August',
|
|
||||||
'September',
|
|
||||||
'October',
|
|
||||||
'November',
|
|
||||||
'December'
|
|
||||||
];
|
|
||||||
|
|
||||||
Future<void> _selectDate(BuildContext context) async {
|
@override
|
||||||
final DateTime? picked = await showDatePicker(
|
void initState() {
|
||||||
context: context,
|
super.initState();
|
||||||
builder: (BuildContext context, child) {
|
isOtherDateSectionOpen = false;
|
||||||
return Theme(
|
showDateSelector = false;
|
||||||
data: Theme.of(context).copyWith(
|
|
||||||
dialogBackgroundColor: kSecondaryColor,
|
|
||||||
primaryColor: kPrimaryColor,
|
|
||||||
colorScheme: const ColorScheme.light(
|
|
||||||
primary: kPrimaryColor,
|
|
||||||
onPrimary: Colors.white,
|
|
||||||
onSurface: Colors.white,
|
|
||||||
),
|
|
||||||
textButtonTheme: TextButtonThemeData(
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
primary: kPrimaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: child!);
|
|
||||||
},
|
|
||||||
initialDate: selectedDate,
|
|
||||||
currentDate: DateTime.now(),
|
|
||||||
selectableDayPredicate: (DateTime date) =>
|
|
||||||
date.isAfter(DateTime.now()) ? false : true,
|
|
||||||
firstDate: DateTime(1901),
|
|
||||||
lastDate: DateTime(2101));
|
|
||||||
if (picked != null && picked != selectedDate) {
|
|
||||||
setState(() {
|
|
||||||
selectedDate = picked;
|
|
||||||
print(selectedDate);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _selectTime(BuildContext context) async {
|
selectedTime = TimeOfDay.now();
|
||||||
final TimeOfDay? timeOfDay = await showTimePicker(
|
selectedDate = DateTime.now();
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context, child) {
|
months = [
|
||||||
return Theme(
|
'January',
|
||||||
data: ThemeData.dark().copyWith(
|
'February',
|
||||||
primaryColor: kPrimaryColor,
|
'March',
|
||||||
timePickerTheme: const TimePickerThemeData(
|
'April',
|
||||||
backgroundColor: kSecondaryColor,
|
'May',
|
||||||
dialTextColor: Colors.white,
|
'June',
|
||||||
),
|
'July',
|
||||||
textButtonTheme: TextButtonThemeData(
|
'August',
|
||||||
style: TextButton.styleFrom(
|
'September',
|
||||||
primary: Colors.white,
|
'October',
|
||||||
),
|
'November',
|
||||||
),
|
'December'
|
||||||
),
|
];
|
||||||
child: child!);
|
|
||||||
},
|
fToast = FToast();
|
||||||
initialTime: selectedTime,
|
fToast.init(context);
|
||||||
initialEntryMode: TimePickerEntryMode.dial,
|
|
||||||
);
|
|
||||||
if (timeOfDay != null && timeOfDay != selectedTime) {
|
|
||||||
setState(() {
|
|
||||||
selectedTime = timeOfDay;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -121,7 +77,9 @@ class _ShowPageAddWatchTimeState extends State<ShowPageAddWatchTime> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
markAsWatched(date: DateTime.now(), time: TimeOfDay.now());
|
||||||
|
},
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 25, vertical: 10)),
|
const EdgeInsets.symmetric(horizontal: 25, vertical: 10)),
|
||||||
@ -145,7 +103,11 @@ class _ShowPageAddWatchTimeState extends State<ShowPageAddWatchTime> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
markAsWatched(
|
||||||
|
date: DateTime.parse(widget.show.releaseDate),
|
||||||
|
time: TimeOfDay.fromDateTime(DateTime.parse("00:00")));
|
||||||
|
},
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 25, vertical: 10)),
|
const EdgeInsets.symmetric(horizontal: 25, vertical: 10)),
|
||||||
@ -332,4 +294,86 @@ class _ShowPageAddWatchTimeState extends State<ShowPageAddWatchTime> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _selectDate(BuildContext context) async {
|
||||||
|
final DateTime? picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context, child) {
|
||||||
|
return Theme(
|
||||||
|
data: Theme.of(context).copyWith(
|
||||||
|
dialogBackgroundColor: kSecondaryColor,
|
||||||
|
primaryColor: kPrimaryColor,
|
||||||
|
colorScheme: const ColorScheme.light(
|
||||||
|
primary: kPrimaryColor,
|
||||||
|
onPrimary: Colors.white,
|
||||||
|
onSurface: Colors.white,
|
||||||
|
),
|
||||||
|
textButtonTheme: TextButtonThemeData(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
primary: kPrimaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: child!);
|
||||||
|
},
|
||||||
|
initialDate: selectedDate,
|
||||||
|
currentDate: DateTime.now(),
|
||||||
|
selectableDayPredicate: (DateTime date) =>
|
||||||
|
date.isAfter(DateTime.now()) ? false : true,
|
||||||
|
firstDate: DateTime(1901),
|
||||||
|
lastDate: DateTime(2101));
|
||||||
|
if (picked != null && picked != selectedDate) {
|
||||||
|
setState(() {
|
||||||
|
selectedDate = picked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _selectTime(BuildContext context) async {
|
||||||
|
final TimeOfDay? timeOfDay = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context, child) {
|
||||||
|
return Theme(
|
||||||
|
data: ThemeData.dark().copyWith(
|
||||||
|
primaryColor: kPrimaryColor,
|
||||||
|
timePickerTheme: const TimePickerThemeData(
|
||||||
|
backgroundColor: kSecondaryColor,
|
||||||
|
dialTextColor: Colors.white,
|
||||||
|
),
|
||||||
|
textButtonTheme: TextButtonThemeData(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
primary: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: child!);
|
||||||
|
},
|
||||||
|
initialTime: selectedTime,
|
||||||
|
initialEntryMode: TimePickerEntryMode.dial,
|
||||||
|
);
|
||||||
|
if (timeOfDay != null && timeOfDay != selectedTime) {
|
||||||
|
setState(() {
|
||||||
|
selectedTime = timeOfDay;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
markAsWatched({required DateTime date, required TimeOfDay time}) async {
|
||||||
|
_preferencesShareholder.addShowToList(
|
||||||
|
fullShow: widget.show, listName: "history", date: date, time: time);
|
||||||
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
Navigator.pop(context);
|
||||||
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
|
fToast.showToast(
|
||||||
|
child: ToastWidget(
|
||||||
|
mainText: "Saved to History}",
|
||||||
|
buttonText: "See list",
|
||||||
|
buttonColor: kAccentColor,
|
||||||
|
buttonOnTap: () {},
|
||||||
|
),
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
toastDuration: const Duration(seconds: 3),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
@ -12,7 +12,7 @@ import '../../../modules/preferences_shareholder.dart';
|
|||||||
import 'get_show_info.dart';
|
import 'get_show_info.dart';
|
||||||
import 'sections/bottom_bar/bottom_bar.dart';
|
import 'sections/bottom_bar/bottom_bar.dart';
|
||||||
import 'sections/index.dart';
|
import 'sections/index.dart';
|
||||||
import 'sections/watchtime.dart';
|
import 'sections/bottom_bar/watchtime.dart';
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class ShowPage extends StatefulWidget {
|
class ShowPage extends StatefulWidget {
|
||||||
@ -90,12 +90,15 @@ class _ShowPageState extends State<ShowPage> with TickerProviderStateMixin {
|
|||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
height: _isBottomAppBarVisible ? 60 : 0.0,
|
height: _isBottomAppBarVisible ? 60 : 0.0,
|
||||||
child: const BottomAppBar(
|
child: BottomAppBar(
|
||||||
shape: CircularNotchedRectangle(),
|
shape: const CircularNotchedRectangle(),
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
notchMargin: 7.5,
|
notchMargin: 7.5,
|
||||||
color: kSecondaryColor,
|
color: kSecondaryColor,
|
||||||
child: ShowPageBottonBar()),
|
child: ShowPageBottonBar(
|
||||||
|
show: show,
|
||||||
|
isThereInLists: _isThereInLists,
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -112,7 +115,7 @@ class _ShowPageState extends State<ShowPage> with TickerProviderStateMixin {
|
|||||||
duration: const Duration(milliseconds: 225),
|
duration: const Duration(milliseconds: 225),
|
||||||
vsync: this),
|
vsync: this),
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ShowPageAddWatchTime(
|
return ShowPageAddWatchDate(
|
||||||
show: show,
|
show: show,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
67
lib/widgets/buttons/activeable_button.dart
Normal file
67
lib/widgets/buttons/activeable_button.dart
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ActiveableButton extends StatelessWidget {
|
||||||
|
final String text;
|
||||||
|
final String? activeText;
|
||||||
|
final IconData icon;
|
||||||
|
final IconData? activeIcon;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final bool isActive;
|
||||||
|
final Color backgroundColor;
|
||||||
|
final Color? activeColor;
|
||||||
|
final EdgeInsets margin;
|
||||||
|
const ActiveableButton(
|
||||||
|
{Key? key,
|
||||||
|
required this.isActive,
|
||||||
|
required this.text,
|
||||||
|
this.activeText,
|
||||||
|
required this.icon,
|
||||||
|
this.activeIcon,
|
||||||
|
required this.onTap,
|
||||||
|
this.activeColor,
|
||||||
|
this.margin = const EdgeInsets.symmetric(vertical: 7.5),
|
||||||
|
this.backgroundColor = const Color(0xff2a425f)})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedContainer(
|
||||||
|
height: 50,
|
||||||
|
margin: margin,
|
||||||
|
width: MediaQuery.of(context).size.width - 100,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isActive
|
||||||
|
? activeColor?.withOpacity(0.75) ??
|
||||||
|
backgroundColor.withOpacity(0.75)
|
||||||
|
: backgroundColor.withOpacity(0.75),
|
||||||
|
borderRadius: BorderRadius.circular(15)),
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: onTap,
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 25),
|
||||||
|
child: Icon(
|
||||||
|
isActive ? activeIcon ?? icon : icon,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
isActive ? activeText ?? text : text,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w800),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
50
lib/widgets/toast.dart
Normal file
50
lib/widgets/toast.dart
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
|
class ToastWidget extends StatelessWidget {
|
||||||
|
final String mainText;
|
||||||
|
final String buttonText;
|
||||||
|
final Color buttonColor;
|
||||||
|
final VoidCallback buttonOnTap;
|
||||||
|
const ToastWidget(
|
||||||
|
{Key? key,
|
||||||
|
required this.mainText,
|
||||||
|
required this.buttonText,
|
||||||
|
required this.buttonColor,
|
||||||
|
required this.buttonOnTap})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
width: double.infinity,
|
||||||
|
height: 50,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(10.0),
|
||||||
|
color: Colors.white.withOpacity(0.95),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
mainText,
|
||||||
|
style: GoogleFonts.ubuntu(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: buttonOnTap,
|
||||||
|
style:
|
||||||
|
TextButton.styleFrom(primary: buttonColor.withOpacity(0.5)),
|
||||||
|
child: Text(
|
||||||
|
buttonText,
|
||||||
|
style: GoogleFonts.ubuntu(
|
||||||
|
fontWeight: FontWeight.w600, color: buttonColor),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
37
pubspec.lock
37
pubspec.lock
@ -15,6 +15,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.1.0"
|
||||||
|
ansicolor:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: ansicolor
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -258,6 +265,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
flutter_riverpod:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_riverpod
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
flutter_slidable:
|
flutter_slidable:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -289,6 +303,13 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
fluttertoast:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: fluttertoast
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "8.0.9"
|
||||||
font_awesome_flutter:
|
font_awesome_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -604,6 +625,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
riverpod:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: riverpod
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -770,6 +798,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
version: "1.10.0"
|
||||||
|
state_notifier:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: state_notifier
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.2+1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -912,4 +947,4 @@ packages:
|
|||||||
version: "3.1.0"
|
version: "3.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.17.0-206.0.dev <3.0.0"
|
dart: ">=2.17.0-206.0.dev <3.0.0"
|
||||||
flutter: ">=2.10.0-0"
|
flutter: ">=3.0.0"
|
||||||
|
@ -19,6 +19,7 @@ dependencies:
|
|||||||
font_awesome_flutter: ^10.1.0
|
font_awesome_flutter: ^10.1.0
|
||||||
google_fonts: ^3.0.1
|
google_fonts: ^3.0.1
|
||||||
ms_undraw: ^3.0.1+1
|
ms_undraw: ^3.0.1+1
|
||||||
|
fluttertoast: ^8.0.9
|
||||||
flutter_spinkit: ^5.1.0
|
flutter_spinkit: ^5.1.0
|
||||||
flutter_rating_bar: ^4.0.0
|
flutter_rating_bar: ^4.0.0
|
||||||
google_nav_bar: ^5.0.5
|
google_nav_bar: ^5.0.5
|
||||||
@ -35,6 +36,9 @@ dependencies:
|
|||||||
http: ^0.13.4
|
http: ^0.13.4
|
||||||
cached_network_image: ^3.2.0
|
cached_network_image: ^3.2.0
|
||||||
speech_to_text: ^5.6.0
|
speech_to_text: ^5.6.0
|
||||||
|
flutter_riverpod: ^1.0.4
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@ -43,6 +47,7 @@ dev_dependencies:
|
|||||||
test: ^1.17.12
|
test: ^1.17.12
|
||||||
build_runner: ^2.1.7
|
build_runner: ^2.1.7
|
||||||
hive_generator: ^1.1.2
|
hive_generator: ^1.1.2
|
||||||
|
ansicolor: ^2.0.1
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
23
res/layout/toast_custom.xml
Normal file
23
res/layout/toast_custom.xml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginStart="50dp"
|
||||||
|
android:background="@drawable/corner"
|
||||||
|
android:layout_marginEnd="50dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="#CC000000"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingTop="0dp"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:paddingBottom="0dp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
tools:text="Toast should be short." />
|
||||||
|
</FrameLayout>
|
Reference in New Issue
Block a user