renamed app to anixstream

This commit is contained in:
Benedict Xavier Wanyonyi
2024-05-31 18:12:02 +03:00
parent f24912fd1c
commit 7e9060bb2d
126 changed files with 47 additions and 57 deletions

View File

@@ -0,0 +1,61 @@
<AnimeScreenView>:
md_bg_color: self.theme_cls.backgroundColor
header:header
side_bar:side_bar
rankings_bar:rankings_bar
anime_description:anime_description
anime_characters:anime_characters
anime_reviews:anime_reviews
MDBoxLayout:
orientation: 'vertical'
MDBoxLayout:
orientation: 'vertical'
size_hint_y:None
height: self.minimum_height
MDBoxLayout:
adaptive_height:True
MDIconButton:
icon: "arrow-left"
on_release:
root.manager_screens.current = root.caller_screen_name
MDScrollView:
size_hint:1,1
MDBoxLayout:
id:main_container
size_hint_y:None
padding:"10dp"
spacing:"10dp"
height: self.minimum_height
orientation:"vertical"
AnimeHeader:
id:header
MDBoxLayout:
size_hint_y:None
height: self.minimum_height
AnimeSideBar:
id:side_bar
screen:root
size_hint_y:None
height:max(self.parent.height,self.minimum_height)
MDBoxLayout:
spacing:"10dp"
orientation:"vertical"
size_hint_y:None
height: max(self.parent.height,self.minimum_height)
RankingsBar:
id:rankings_bar
Controls:
screen:root
cols:3 if root.width < 1100 else 5
MDBoxLayout:
adaptive_height:True
padding:"20dp"
orientation:"vertical"
AnimeDescription:
id:anime_description
AnimeCharacters:
id:anime_characters
AnimeReviews:
id:anime_reviews
BoxLayout:

View File

@@ -0,0 +1,136 @@
from datetime import datetime
from kivy.properties import ObjectProperty, DictProperty, StringProperty
from Utility import anilist_data_helper
from libs.anilist import AnilistBaseMediaDataSchema
from View.base_screen import BaseScreenView
from .components import (
AnimeHeader,
AnimeSideBar,
AnimeDescription,
AnimeReviews,
AnimeCharacters,
AnimdlStreamDialog,
DownloadAnimeDialog,
RankingsBar,
)
class AnimeScreenView(BaseScreenView):
"""The anime screen view"""
caller_screen_name = StringProperty()
header: AnimeHeader = ObjectProperty()
side_bar: AnimeSideBar = ObjectProperty()
rankings_bar: RankingsBar = ObjectProperty()
anime_description: AnimeDescription = ObjectProperty()
anime_characters: AnimeCharacters = ObjectProperty()
anime_reviews: AnimeReviews = ObjectProperty()
data = DictProperty()
anime_id = 0
def update_layout(self, data: AnilistBaseMediaDataSchema, caller_screen_name: str):
self.caller_screen_name = caller_screen_name
self.data = data
# uitlity functions
# variables
english_title = data["title"]["english"]
jp_title = data["title"]["romaji"]
studios = data["studios"]["nodes"]
# update header
self.header.titles = f"{english_title}\n{jp_title}"
if banner_image := data["bannerImage"]:
self.header.banner_image = banner_image
# -----side bar-----
# update image
self.side_bar.image = data["coverImage"]["extraLarge"]
# update alternative titles
alternative_titles = {
"synonyms": anilist_data_helper.format_list_data_with_comma(
data["synonyms"]
), # list
"japanese": jp_title,
"english": english_title,
}
self.side_bar.alternative_titles = alternative_titles
# update information
information = {
"episodes": data["episodes"],
"status": data["status"],
"nextAiringEpisode": anilist_data_helper.extract_next_airing_episode(
data["nextAiringEpisode"]
),
"aired": f"{anilist_data_helper.format_anilist_date_object(data['startDate'])} to {anilist_data_helper.format_anilist_date_object(data['endDate'])}",
"premiered": f"{data['season']} {data['seasonYear']}",
"broadcast": data["format"],
"countryOfOrigin": data["countryOfOrigin"],
"hashtag": data["hashtag"],
"studios": anilist_data_helper.format_list_data_with_comma(
[studio["name"] for studio in studios if studio["isAnimationStudio"]]
), # { "name": "Sunrise", "isAnimationStudio": true }
"producers": anilist_data_helper.format_list_data_with_comma(
[
studio["name"]
for studio in studios
if not studio["isAnimationStudio"]
]
),
"source": data["source"],
"genres": anilist_data_helper.format_list_data_with_comma(data["genres"]),
"duration": data["duration"],
}
self.side_bar.information = information
# update statistics
statistics = [*[(stat["context"], stat["rank"]) for stat in data["rankings"]]]
self.side_bar.statistics = statistics
# update tags
self.side_bar.tags = [(tag["name"], tag["rank"]) for tag in data["tags"]]
# update external links
external_links = [
("AniList", data["siteUrl"]),
*[(site["site"], site["url"]) for site in data["externalLinks"]],
]
self.side_bar.external_links = external_links
self.rankings_bar.rankings = {
"Popularity": data["popularity"],
"Favourites": data["favourites"],
"AverageScore": data["averageScore"] if data["averageScore"] else 0,
}
self.anime_description.description = data["description"]
self.anime_characters.characters = [
(character["node"], character["voiceActors"])
for character in data["characters"]["edges"]
] # list (character,actor)
self.anime_reviews.reviews = data["reviews"]["nodes"]
def stream_anime_with_custom_cmds_dialog(self,mpv=False):
"""
Called when user wants to stream with custom commands
"""
AnimdlStreamDialog(self.data,mpv).open()
def open_download_anime_dialog(self):
"""
Opens the download anime dialog
"""
DownloadAnimeDialog(self.data).open()
def add_to_user_anime_list(self, *args):
self.app.add_anime_to_user_anime_list(self.model.anime_id)

View File

@@ -0,0 +1,10 @@
from .side_bar import AnimeSideBar
from .header import AnimeHeader
from .rankings_bar import RankingsBar
from .controls import Controls
from .description import AnimeDescription
from .characters import AnimeCharacters
from .review import AnimeReviews
from .animdl_stream_dialog import AnimdlStreamDialog
from .download_anime_dialog import DownloadAnimeDialog

View File

@@ -0,0 +1,63 @@
<StreamDialogLabel@MDLabel>:
adaptive_height:True
max_lines:0
shorten:False
markup:True
font_style: "Label"
role: "medium"
bold:True
<StreamDialogHeaderLabel@MDLabel>:
adaptive_height:True
halign:"center"
max_lines:0
shorten:False
bold:True
markup:True
font_style: "Title"
role: "medium"
md_bg_color:self.theme_cls.secondaryContainerColor
padding:"10dp"
<AnimdlStreamDialog>
md_bg_color:self.theme_cls.backgroundColor
radius:8
size_hint:None,None
height:"500dp"
width:"400dp"
MDBoxLayout:
spacing: '10dp'
padding:"10dp"
orientation:"vertical"
StreamDialogHeaderLabel:
text:"Stream Anime"
StreamDialogLabel:
text:"Title"
MDTextField:
id:title_field
required:True
StreamDialogLabel:
text:"Range"
MDTextField:
id:range_field
required:True
StreamDialogLabel:
text:"Latest"
MDTextField:
id:latest_field
required:True
StreamDialogLabel:
text:"Quality"
MDTextField:
id:quality_field
required:True
MDBoxLayout:
orientation:"vertical"
MDButton:
pos_hint: {'center_x': 0.5}
on_press:root.stream_anime(app)
MDButtonIcon:
icon:"rss"
MDButtonText:
text:"Stream"

View File

@@ -0,0 +1,69 @@
from kivy.clock import Clock
from kivy.uix.modalview import ModalView
from kivymd.uix.behaviors import (
StencilBehavior,
CommonElevationBehavior,
BackgroundColorBehavior,
)
from kivymd.theming import ThemableBehavior
class AnimdlStreamDialog(
ThemableBehavior,
StencilBehavior,
CommonElevationBehavior,
BackgroundColorBehavior,
ModalView,
):
"""The anime streaming dialog"""
def __init__(self, data, mpv, **kwargs):
super().__init__(**kwargs)
self.data = data
self.mpv = mpv
if title := data["title"].get("romaji"):
self.ids.title_field.text = title
elif title := data["title"].get("english"):
self.ids.title_field.text = title
self.ids.quality_field.text = "best"
def _stream_anime(self, app):
if self.mpv:
streaming_cmds = {}
title = self.ids.title_field.text
streaming_cmds["title"] = title
episodes_range = self.ids.range_field.text
if episodes_range:
streaming_cmds["episodes_range"] = episodes_range
quality = self.ids.quality_field.text
if quality:
streaming_cmds["quality"] = quality
else:
streaming_cmds["quality"] = "best"
app.watch_on_animdl(streaming_cmds)
else:
cmds = []
title = self.ids.title_field.text
cmds.append(title)
episodes_range = self.ids.range_field.text
if episodes_range:
cmds = [*cmds, "-r", episodes_range]
latest = self.ids.latest_field.text
if latest:
cmds = [*cmds, "-s", latest]
quality = self.ids.quality_field.text
if quality:
cmds = [*cmds, "-q", quality]
app.watch_on_animdl(custom_options=cmds)
self.dismiss()
def stream_anime(self, app):
Clock.schedule_once(lambda _: self._stream_anime(app))

View File

@@ -0,0 +1,71 @@
#:import get_hex_from_color kivy.utils.get_hex_from_color
<CharactersContainer@MDBoxLayout>:
adaptive_height:True
md_bg_color:self.theme_cls.surfaceContainerLowColor
padding:"10dp"
orientation:"vertical"
<CharacterText@MDLabel>:
adaptive_height:True
max_lines:0
shorten:False
markup:True
font_style: "Body"
role: "small"
<CharacterHeader@MDBoxLayout>:
adaptive_height:True
spacing:"10dp"
<CharacterAvatar@FitImage>
radius:50
size_hint:None,None
height:"50dp"
width:"50dp"
<CharacterSecondaryContainer@MDBoxLayout>:
adaptive_height:True
orientation:"vertical"
<AnimeCharacter>:
spacing:"5dp"
adaptive_height:True
orientation:"vertical"
CharacterHeader:
padding:"10dp"
CharacterAvatar:
source:root.character["image"]
CharacterText:
text: root.character["name"]
pos_hint:{"center_y":.5}
CharacterSecondaryContainer:
spacing:"5dp"
MDDivider:
CharacterText:
text: "Details"
MDDivider:
CharacterText:
text:"[color={}]Gender:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.character["gender"])
CharacterText:
text:"[color={}]Date Of Birth:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.character["dateOfBirth"])
CharacterText:
text:"[color={}]Age:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.character["age"])
CharacterText:
text:"[color={}]Description:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.character["description"])
max_lines:5
CharacterText:
text:"[color={}]Voice Actors:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.voice_actors["name"])
MDDivider:
<AnimeCharacters>:
adaptive_height:True
container:container
orientation:"vertical"
HeaderLabel:
text:"Characters"
halign:"left"
CharactersContainer:
id:container

View File

@@ -0,0 +1,56 @@
from kivy.clock import Clock
from kivy.properties import ObjectProperty, ListProperty
from kivymd.uix.boxlayout import MDBoxLayout
class AnimeCharacter(MDBoxLayout):
"""an Anime character data"""
voice_actors = ObjectProperty({"name": "", "image": ""})
character = ObjectProperty(
{
"name": "",
"gender": "",
"dateOfBirth": "",
"image": "",
"age": "",
"description": "",
}
)
class AnimeCharacters(MDBoxLayout):
"""The anime characters card"""
container = ObjectProperty()
characters = ListProperty()
def update_characters_card(self, instance, characters):
format_date = lambda date_: (
f"{date_['day']}/{date_['month']}/{date_['year']}" if date_ else ""
)
self.container.clear_widgets()
for character_ in characters: # character (character,actor)
character = character_[0]
actors = character_[1]
anime_character = AnimeCharacter()
anime_character.character = {
"name": character["name"]["full"],
"gender": character["gender"],
"dateOfBirth": format_date(character["dateOfBirth"]),
"image": character["image"]["medium"],
"age": character["age"],
"description": character["description"],
}
anime_character.voice_actors = {
"name": ", ".join([actor["name"]["full"] for actor in actors])
}
# anime_character.voice_actor =
self.container.add_widget(anime_character)
def on_characters(self, *args):
Clock.schedule_once(lambda _: self.update_characters_card(*args))

View File

@@ -0,0 +1,32 @@
<Controls>
adaptive_height:True
padding:"10dp"
spacing:"10dp"
pos_hint: {'center_x': 0.5}
cols:3
MDButton:
on_press:
root.screen.add_to_user_anime_list()
add_to_user_list_label.text = "Added to MyAnimeList"
MDButtonText:
id:add_to_user_list_label
text:"Add to MyAnimeList"
MDButton:
on_press:
if root.screen:root.screen.stream_anime_with_custom_cmds_dialog()
MDButtonText:
text:"Watch on Animdl"
MDButton:
on_press:
if root.screen:root.screen.stream_anime_with_custom_cmds_dialog(mpv=True)
MDButtonText:
text:"Watch on mpv"
MDButton:
on_press: app.watch_on_allanime(root.screen.data["title"]["romaji"]) if root.screen.data["title"]["romaji"] else app.watch_on_allanime(root.screen.data["title"]["english"])
MDButtonText:
text:"Watch on AllAnime"
MDButton:
on_press:
if root.screen:root.screen.open_download_anime_dialog()
MDButtonText:
text:"Download Anime"

View File

@@ -0,0 +1,9 @@
from kivy.properties import ObjectProperty
from kivymd.uix.gridlayout import MDGridLayout
class Controls(MDGridLayout):
"""The diferent controls available"""
screen = ObjectProperty()

View File

@@ -0,0 +1,23 @@
<DescriptionContainer@MDBoxLayout>:
adaptive_height:True
md_bg_color:self.theme_cls.surfaceContainerLowColor
padding:"10dp"
<DescriptionText@MDLabel>:
adaptive_height:True
max_lines:0
shorten:False
markup:True
font_style: "Body"
role: "small"
<AnimeDescription>:
orientation:"vertical"
adaptive_height:True
HeaderLabel:
halign:"left"
text:"Description"
DescriptionContainer:
DescriptionText:
text:root.description

View File

@@ -0,0 +1,9 @@
from kivy.properties import StringProperty
from kivymd.uix.boxlayout import MDBoxLayout
class AnimeDescription(MDBoxLayout):
"""The anime description"""
description = StringProperty()

View File

@@ -0,0 +1,58 @@
<DownloadDialogLabel@MDLabel>:
adaptive_height:True
max_lines:0
shorten:False
markup:True
font_style: "Label"
role: "medium"
bold:True
<DownloadDialogHeaderLabel@MDLabel>:
adaptive_height:True
halign:"center"
max_lines:0
shorten:False
bold:True
markup:True
font_style: "Title"
role: "medium"
md_bg_color:self.theme_cls.secondaryContainerColor
padding:"10dp"
<DownloadAnimeDialog>
md_bg_color:self.theme_cls.backgroundColor
radius:8
size_hint:None,None
height:"500dp"
width:"400dp"
MDBoxLayout:
spacing: '10dp'
padding:"10dp"
orientation:"vertical"
DownloadDialogHeaderLabel:
text:"Download Anime"
DownloadDialogLabel:
text:"Title"
MDTextField:
id:title_field
required:True
DownloadDialogLabel:
text:"Range"
MDTextField:
id:range_field
required:True
DownloadDialogLabel:
text:"Quality"
MDTextField:
id:quality_field
required:True
MDBoxLayout:
orientation:"vertical"
MDButton:
pos_hint: {'center_x': 0.5}
on_press:root.download_anime(app)
MDButtonIcon:
icon:"download"
MDButtonText:
text:"Download"

View File

@@ -0,0 +1,42 @@
from kivy.uix.modalview import ModalView
from kivymd.uix.behaviors import (
StencilBehavior,
CommonElevationBehavior,
BackgroundColorBehavior,
)
from kivymd.theming import ThemableBehavior
# from main import AniXStreamApp
class DownloadAnimeDialog(
ThemableBehavior,
StencilBehavior,
CommonElevationBehavior,
BackgroundColorBehavior,
ModalView,
):
"""The download anime dialog"""
def __init__(self, data, **kwargs):
super(DownloadAnimeDialog, self).__init__(**kwargs)
self.data = data
self.anime_id = self.data["id"]
if title := data["title"].get("romaji"):
self.ids.title_field.text = title
elif title := data["title"].get("english"):
self.ids.title_field.text = title
self.ids.quality_field.text = "best"
def download_anime(self, app):
default_cmds = {}
title = self.ids.title_field.text
default_cmds["title"] = title
if episodes_range := self.ids.range_field.text:
default_cmds["episodes_range"] = episodes_range
if quality := self.ids.range_field.text:
default_cmds["quality"] = quality
app.download_anime(self.anime_id, default_cmds)
self.dismiss()

View File

@@ -0,0 +1,20 @@
<AnimeHeader>:
adaptive_height:True
orientation: 'vertical'
MDBoxLayout:
adaptive_height:True
md_bg_color:self.theme_cls.secondaryContainerColor
MDLabel:
text: root.titles
adaptive_height:True
padding:"5dp"
bold:True
shorten:False
max_lines:2
font_style:"Label"
role:"large"
FitImage:
size_hint_y: None
height: dp(250)
source: root.banner_image if root.banner_image else app.default_banner_image

View File

@@ -0,0 +1,8 @@
from kivy.properties import StringProperty
from kivymd.uix.boxlayout import MDBoxLayout
class AnimeHeader(MDBoxLayout):
titles = StringProperty()
banner_image = StringProperty()

View File

@@ -0,0 +1,81 @@
#:set yellow [.9,.9,0,.9]
<RankingsLabel@MDLabel>:
max_lines:0
shorten:False
markup:True
font_style: "Label"
role: "medium"
<RankingsHeaderLabel@MDLabel>:
color:self.theme_cls.primaryColor
bold:True
max_lines:0
shorten:False
font_style: "Label"
role: "large"
<RankingsDivider@MDDivider>:
orientation:"vertical"
<RankingsBoxLayout@MDBoxLayout>:
orientation:"vertical"
padding:"20dp"
<RankingsBar>:
size_hint_y:None
height:dp(100)
line_color:self.theme_cls.secondaryColor
padding:"10dp"
RankingsBoxLayout:
size_hint_x:.4
RankingsHeaderLabel:
text:"Average Score"
MDBoxLayout:
adaptive_width:True
MDBoxLayout:
adaptive_size:True
pos_hint: {'center_y': .5}
MDIcon:
icon: "star"
color:yellow
disabled: not((root.rankings["AverageScore"]/100)*6>=1)
MDIcon:
color:yellow
disabled: not(root.rankings["AverageScore"]/100*6>=2)
icon: "star"
MDIcon:
color:yellow
disabled: not(root.rankings["AverageScore"]/100*6>=3)
icon: "star"
MDIcon:
color:yellow
disabled: not(root.rankings["AverageScore"]/100*6>=4)
icon: "star"
MDIcon:
color:yellow
icon: "star"
disabled: not(root.rankings["AverageScore"]/100*6>=5)
MDIcon:
color:yellow
icon: "star"
disabled: not(root.rankings["AverageScore"]/100*6>=6)
RankingsLabel:
adaptive_width:True
text: '{}'.format(root.rankings["AverageScore"]/10)
RankingsDivider:
RankingsBoxLayout:
size_hint_x:.3
RankingsHeaderLabel:
text:"Popularity"
RankingsLabel:
text: '{}'.format(root.rankings["Popularity"])
RankingsDivider:
RankingsBoxLayout:
size_hint_x:.3
RankingsHeaderLabel:
text:"Favourites"
RankingsLabel:
text: '{}'.format(root.rankings["Favourites"])

View File

@@ -0,0 +1,13 @@
from kivy.properties import DictProperty
from kivymd.uix.boxlayout import MDBoxLayout
class RankingsBar(MDBoxLayout):
rankings = DictProperty(
{
"Popularity": 0,
"Favourites": 0,
"AverageScore": 0,
}
)

View File

@@ -0,0 +1,50 @@
#:import get_hex_from_color kivy.utils.get_hex_from_color
<ReviewContainer@MDBoxLayout>:
adaptive_height:True
md_bg_color:self.theme_cls.surfaceContainerLowColor
padding:"10dp"
orientation:"vertical"
<ReviewText@MDLabel>:
adaptive_height:True
max_lines:0
shorten:False
markup:True
font_style: "Body"
role: "small"
<ReviewHeader@MDBoxLayout>:
adaptive_height:True
spacing:"10dp"
padding:"10dp"
<ReviewerAvatar@FitImage>
radius:50
size_hint:None,None
height:"50dp"
width:"50dp"
<AnimeReview>
orientation:"vertical"
adaptive_height:True
ReviewHeader:
ReviewerAvatar:
source:root.review["avatar"]
ReviewText:
pos_hint: {'center_y': 0.5}
text:root.review["username"]
MDDivider:
ReviewText:
text:root.review["summary"]
MDDivider:
<AnimeReviews>:
container:container
adaptive_height:True
orientation:"vertical"
HeaderLabel:
halign:"left"
text:"reviews"
ReviewContainer:
id:container

View File

@@ -0,0 +1,29 @@
from kivy.properties import ObjectProperty, ListProperty
from kivy.clock import Clock
from kivymd.uix.boxlayout import MDBoxLayout
class AnimeReview(MDBoxLayout):
review = ObjectProperty({"username": "", "avatar": "", "summary": ""})
class AnimeReviews(MDBoxLayout):
"""anime reviews"""
reviews = ListProperty()
container = ObjectProperty()
def on_reviews(self, *args):
Clock.schedule_once(lambda _: self.update_reviews_card(*args))
def update_reviews_card(self, instance, reviews):
self.container.clear_widgets()
for review in reviews:
review_ = AnimeReview()
review_.review = {
"username": review["user"]["name"],
"avatar": review["user"]["avatar"]["medium"],
"summary": review["summary"],
}
self.container.add_widget(review_)

View File

@@ -0,0 +1,102 @@
#:import get_hex_from_color kivy.utils.get_hex_from_color
<FitBoxLayout@MDBoxLayout>:
size_hint_y:None
height:self.minimum_height
padding:"10dp"
spacing:"10dp"
orientation: 'vertical'
pos_hint: {'center_x': 0.5}
<SideBarLabel>:
adaptive_height:True
max_lines:0
shorten:False
markup:True
font_style: "Label"
role: "medium"
<HeaderLabel>:
adaptive_height:True
md_bg_color:self.theme_cls.secondaryContainerColor
MDLabel:
text:root.text
adaptive_height:True
halign:root.halign
max_lines:0
shorten:False
bold:True
font_style: "Label"
role: "large"
padding:"10dp"
<AnimeSideBar>:
size_hint_x: None
width: dp(300)
orientation: 'vertical'
line_color:self.theme_cls.secondaryColor
statistics_container:statistics_container
tags_container:tags_container
external_links_container:external_links_container
FitBoxLayout:
FitImage:
source:root.image
size_hint:None,None
height:dp(250)
width:dp(200)
pos_hint: {'center_x': 0.5}
MDButton:
pos_hint: {'center_x': 0.5}
on_press:
if root.screen:root.screen.stream_anime_with_custom_cmds_dialog(mpv=True)
MDButtonText:
text:"Watch with mpv"
FitBoxLayout:
HeaderLabel:
text:"Alternative Titles"
SideBarLabel:
text: "[color={}]Synonyms:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.alternative_titles["synonyms"])
SideBarLabel:
text: "[color={}]English:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.alternative_titles["english"])
SideBarLabel:
text: "[color={}]Japanese:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.alternative_titles["japanese"])
FitBoxLayout:
HeaderLabel:
text:"Information"
SideBarLabel:
text: "[color={}]Episodes:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.information["episodes"])
SideBarLabel:
text: "[color={}]Status:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.information["status"])
SideBarLabel:
text: "[color={}]Next Airing Episode:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.information["nextAiringEpisode"])
SideBarLabel:
text: "[color={}]Aired:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.information["aired"])
SideBarLabel:
text: "[color={}]Premiered:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.information["premiered"])
SideBarLabel:
text: "[color={}]Broadcast:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.information["broadcast"])
SideBarLabel:
text: "[color={}]Country Of Origin:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.information["countryOfOrigin"])
SideBarLabel:
text: "[color={}]Hashtag:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.information["hashtag"])
SideBarLabel:
text: "[color={}]Studios:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.information["studios"])
SideBarLabel:
text: "[color={}]Producers:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.information["producers"])
SideBarLabel:
text: "[color={}]Source:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.information["source"])
SideBarLabel:
text: "[color={}]Genres:[/color] {}".format(get_hex_from_color(self.theme_cls.primaryColor),root.information["genres"])
SideBarLabel:
text: "[color={}]Duration:[/color] {} minutes".format(get_hex_from_color(self.theme_cls.primaryColor),root.information["duration"])
FitBoxLayout:
id:statistics_container
HeaderLabel:
text:"Rankings"
FitBoxLayout:
id:tags_container
HeaderLabel:
text:"Tags"
FitBoxLayout:
id:external_links_container
HeaderLabel:
text:"External Links"
BoxLayout:

View File

@@ -0,0 +1,98 @@
from kivy.properties import ObjectProperty, StringProperty, DictProperty, ListProperty
from kivy.utils import get_hex_from_color
from kivy.factory import Factory
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.label import MDLabel
class HeaderLabel(MDBoxLayout):
text = StringProperty()
halign = StringProperty("center")
Factory.register("HeaderLabel", HeaderLabel)
class SideBarLabel(MDLabel):
pass
# TODO:Switch to using the kivy_markup_module
class AnimeSideBar(MDBoxLayout):
screen = ObjectProperty()
image = StringProperty()
alternative_titles = DictProperty(
{
"synonyms": "",
"english": "",
"japanese": "",
}
)
information = DictProperty(
{
"episodes": "",
"status": "",
"aired": "",
"nextAiringEpisode": "",
"premiered": "",
"broadcast": "",
"countryOfOrigin": "",
"hashtag": "",
"studios": "", # { "name": "Sunrise", "isAnimationStudio": true }
"source": "",
"genres": "",
"duration": "",
"producers": "",
}
)
statistics = ListProperty()
statistics_container = ObjectProperty()
external_links = ListProperty()
external_links_container = ObjectProperty()
tags = ListProperty()
tags_container = ObjectProperty()
def on_statistics(self, instance, value):
self.statistics_container.clear_widgets()
header = HeaderLabel()
header.text = "Rankings"
self.statistics_container.add_widget(header)
for stat in value:
# stat (rank,context)
label = SideBarLabel()
label.text = "[color={}]{}:[/color] {}".format(
get_hex_from_color(label.theme_cls.primaryColor),
stat[0].capitalize(),
f"{stat[1]}",
)
self.statistics_container.add_widget(label)
def on_tags(self, instance, value):
self.tags_container.clear_widgets()
header = HeaderLabel()
header.text = "Tags"
self.tags_container.add_widget(header)
for tag in value:
label = SideBarLabel()
label.text = "[color={}]{}:[/color] {}".format(
get_hex_from_color(label.theme_cls.primaryColor),
tag[0].capitalize(),
f"{tag[1]} %",
)
self.tags_container.add_widget(label)
def on_external_links(self, instance, value):
self.external_links_container.clear_widgets()
header = HeaderLabel()
header.text = "External Links"
self.external_links_container.add_widget(header)
for site in value:
# stat (rank,context)
label = SideBarLabel()
label.text = "[color={}]{}:[/color] {}".format(
get_hex_from_color(label.theme_cls.primaryColor),
site[0].capitalize(),
site[1],
)
self.external_links_container.add_widget(label)

View File

@@ -0,0 +1,21 @@
#:import get_color_from_hex kivy.utils.get_color_from_hex
#:import StringProperty kivy.properties.StringProperty
<CrashLogScreenView>
md_bg_color: self.theme_cls.backgroundColor
# main_container:main_container
MDBoxLayout:
NavRail:
screen:root
MDAnchorLayout:
anchor_y: 'top'
padding:"10dp"
MDBoxLayout:
orientation: 'vertical'
SearchBar:
MDScrollView:
MDLabel:
text:root.crash_text
adaptive_height:True
markup:True
padding:"10dp"

View File

@@ -0,0 +1,21 @@
from kivy.properties import StringProperty
from View.base_screen import BaseScreenView
from Utility.utils import read_crash_file
from Utility.kivy_markup_helper import color_text, bolden
class CrashLogScreenView(BaseScreenView):
"""The crash log screen"""
crash_text = StringProperty()
def __init__(self, **kw):
super().__init__(**kw)
if crashes := read_crash_file():
self.crash_text = crashes
else:
self.crash_text = color_text(
f"No Crashes so far :) and if there are any in the future {bolden('please report! Okay?')}",
self.theme_cls.primaryColor,
)

View File

@@ -0,0 +1,28 @@
#:import color_text Utility.kivy_markup_helper.color_text
<TaskText@MDLabel>:
adaptive_height:True
max_lines:0
shorten:False
markup:True
font_style: "Label"
role: "large"
bold:True
<TaskCard>:
adaptive_height:True
radius:8
padding:"20dp"
md_bg_color:self.theme_cls.surfaceContainerHighColor
TaskText:
size_hint_x:.8
text:color_text(root.anime_task_name,root.theme_cls.primaryColor)
TaskText:
size_hint_x:.2
# color:self.theme_cls.surfaceDimColor
theme_text_color:"Secondary"
text:color_text(root.episodes_to_download,root.theme_cls.secondaryColor)
MDIcon:
icon:"download"

View File

@@ -0,0 +1,12 @@
from kivy.properties import StringProperty
from kivymd.uix.boxlayout import MDBoxLayout
# TODO: add a progress bar to show the individual progress of each task
class TaskCard(MDBoxLayout):
anime_task_name = StringProperty()
episodes_to_download = StringProperty()
def __init__(self, anime_title:str, episodes:str, *args, **kwargs):
super().__init__(*args, **kwargs)
self.anime_task_name = f"{anime_title}"
self.episodes_to_download = f"Episodes: {episodes}"

View File

@@ -0,0 +1,58 @@
#:import get_color_from_hex kivy.utils.get_color_from_hex
#:import StringProperty kivy.properties.StringProperty
<DownloadsScreenLabel@MDLabel>:
adaptive_height:True
max_lines:0
shorten:False
markup:True
font_style: "Label"
role: "large"
bold:True
<DownloadsScreenView>
md_bg_color: self.theme_cls.backgroundColor
main_container:main_container
download_progress_label:download_progress_label
progress_bar:progress_bar
MDBoxLayout:
NavRail:
screen:root
MDAnchorLayout:
anchor_y: 'top'
MDBoxLayout:
orientation: 'vertical'
SearchBar:
MDScrollView:
size_hint:.95,1
MDBoxLayout:
id:main_container
orientation:"vertical"
padding:"40dp"
pos_hint:{"center_x":.5}
spacing:"10dp"
adaptive_height:True
HeaderLabel:
text:"Download Tasks"
halign:"left"
MDIcon:
padding:"10dp"
pos_hint:{"center_y":.5}
icon:"clock"
MDBoxLayout:
size_hint_y:None
height:"40dp"
spacing:"10dp"
padding:"10dp"
md_bg_color:self.theme_cls.secondaryContainerColor
DownloadsScreenLabel:
id:download_progress_label
size_hint_x: .6
text:"Try Downloading sth :)"
pos_hint: {'center_y': .5}
MDLinearProgressIndicator:
id: progress_bar
size_hint_x: .4
size_hint_y:None
height:"10dp"
type: "determinate"
pos_hint: {'center_y': .5}

View File

@@ -0,0 +1,32 @@
from kivy.clock import Clock
from kivy.properties import ObjectProperty
from kivy.logger import Logger
from kivy.utils import format_bytes_to_human
from View.base_screen import BaseScreenView
from .components.task_card import TaskCard
class DownloadsScreenView(BaseScreenView):
main_container = ObjectProperty()
progress_bar = ObjectProperty()
download_progress_label = ObjectProperty()
def on_new_download_task(self, anime_title: str, episodes: str | None):
if not episodes:
episodes = "All"
Clock.schedule_once(
lambda _: self.main_container.add_widget(TaskCard(anime_title, episodes))
)
def on_episode_download_progress(
self, current_bytes_downloaded, total_bytes, episode_info
):
percentage_completion = round((current_bytes_downloaded / total_bytes) * 100)
progress_text = f"Downloading: {episode_info['anime_title']} - {episode_info['episode']} ({format_bytes_to_human(current_bytes_downloaded)}/{format_bytes_to_human(total_bytes)})"
if (percentage_completion % 5) == 0:
self.progress_bar.value = max(min(percentage_completion, 100), 0)
self.download_progress_label.text = progress_text
def update_layout(self, widget):
self.user_anime_list_container.add_widget(widget)

View File

@@ -0,0 +1,60 @@
#:import get_color_from_hex kivy.utils.get_color_from_hex
#:import StringProperty kivy.properties.StringProperty
<HelpCard@MDBoxLayout>
spacing:"10dp"
orientation:"vertical"
adaptive_height:True
md_bg_color:self.theme_cls.surfaceContainerLowColor
theme_text_color:"Secondary"
<HelpHeaderLabel@HeaderLabel>
halign:"left"
<HelpDescription@MDLabel>:
adaptive_height:True
max_lines:0
shorten:False
markup:True
font_style: "Body"
padding:"10dp"
role: "large"
<HelpScreenView>
md_bg_color: self.theme_cls.backgroundColor
# main_container:main_container
MDBoxLayout:
NavRail:
screen:root
MDAnchorLayout:
anchor_y: 'top'
padding:"10dp"
MDBoxLayout:
orientation: 'vertical'
SearchBar:
MDScrollView:
size_hint_x:.95
MDBoxLayout:
adaptive_height:True
padding:"10dp"
orientation:"vertical"
HelpCard:
HelpHeaderLabel:
text:"Animdl Commands"
HelpDescription:
text:root.animdl_help
HelpCard:
HelpHeaderLabel:
text:"Installing Animdl"
HelpDescription:
text:root.installing_animdl_help
HelpCard:
HelpHeaderLabel:
text:"Available Themes"
HelpDescription:
text:root.available_themes
HelpCard:
HelpHeaderLabel:
text:"About"
HelpDescription:
text:"This app was made to be a gui wrapper for any and all anime cli tools. Inoder to solve the age old problem of getting the same experience from the cli as you would in a website"

View File

@@ -0,0 +1,72 @@
from kivy.properties import ObjectProperty, StringProperty
from View.base_screen import BaseScreenView
from Utility.kivy_markup_helper import bolden, color_text, underline
from Utility.data import themes_available
class HelpScreenView(BaseScreenView):
main_container = ObjectProperty()
animdl_help = StringProperty()
installing_animdl_help = StringProperty()
available_themes = StringProperty()
def __init__(self, **kw):
super(HelpScreenView, self).__init__(**kw)
self.animdl_help = f"""
{underline(color_text(bolden('Streaming Commands'),self.theme_cls.surfaceBrightColor))}
{color_text(bolden('-r:'),self.theme_cls.primaryFixedDimColor)} specifies the range of episodes
example: {color_text(('animdl stream <Anime title> -r 1-4'),self.theme_cls.tertiaryColor)}
explanation:in this case gets 4 episodes 1 to 4
{color_text(('-s:'),self.theme_cls.primaryFixedDimColor)} special selector for the most recent episodes or basically selects from the end
example: {color_text(('animdl stream <Anime title> -s 4'),self.theme_cls.tertiaryColor)}
explanation: in this case gets the latest 4 episodes
{color_text(('-q:'),self.theme_cls.primaryFixedDimColor)} sets the quality of the stream
example: {color_text(('animdl stream <Anime title> -q best'),self.theme_cls.tertiaryColor)}
explanation: The quality of the anime stream should be the best possible others include 1080,720... plus worst
{underline(color_text(bolden('Downloading Commands'),self.theme_cls.surfaceBrightColor))}
{color_text(bolden('-r:'),self.theme_cls.primaryFixedDimColor)} specifies the range of episodes
example: {color_text(('animdl download <Anime title> -r 1-4'),self.theme_cls.tertiaryColor)}
explanation:in this case gets 4 episodes 1 to 4
{color_text(('-s:'),self.theme_cls.primaryFixedDimColor)} special selector for the most recent episodes or basically selects from the end
example: {color_text(('animdl download <Anime title> -s 4'),self.theme_cls.tertiaryColor)}
explanation: in this case gets the latest 4 episodes
{color_text(('-q:'),self.theme_cls.primaryFixedDimColor)} sets the quality of the download
example: {color_text(('animdl download <Anime title> -q best'),self.theme_cls.tertiaryColor)}
explanation: The quality of the anime download should be the best possible others include 1080,720... plus worst
"""
self.installing_animdl_help = f"""
This works on windows only and should be done in powershell
1. First install pyenv with the following command:
{color_text(('Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1" -OutFile "./install-pyenv-win.ps1"; &"./install-pyenv-win.ps1"'),self.theme_cls.tertiaryColor)}
2. run the following command to check if successsful:
{color_text(('pyenv --version '),self.theme_cls.tertiaryColor)}
3. run the following command to install python 3.10
{color_text(('pyenv install 3.10'),self.theme_cls.tertiaryColor)}
4. To confirm successful install of python 3.10 run the following command and check if 3.10 is listed:
{color_text(('pyenv -l'),self.theme_cls.tertiaryColor)}
5. Next run:
{color_text(('pyenv local 3.10'),self.theme_cls.tertiaryColor)} (if in anixstream directory to set python 3.10 as local interpreter)
or run:
{color_text(('pyenv global 3.10'),self.theme_cls.tertiaryColor)} (if in another directory to set python version 3.10 as global interpreter)
6. Check if success by running and checking if output is 3.10:
{color_text(('python --version'),self.theme_cls.tertiaryColor)}
7. Run:
{color_text(('python -m pip install animdl'),self.theme_cls.tertiaryColor)}
8. Check if success by running:
{color_text(('python -m animdl'),self.theme_cls.tertiaryColor)}
{color_text(('Note:'),self.theme_cls.secondaryColor)}
All this instructions should be done from the folder you choose to install
aniXstream but incase you have never installed python should work any where
{bolden('-----------------------------')}
Now enjoy :)
{bolden('-----------------------------')}
"""
self.available_themes = "\n".join(themes_available)
def model_is_changed(self) -> None:
"""
Called whenever any change has occurred in the data model.
The view in this method tracks these changes and updates the UI
according to these changes.
"""

View File

View File

@@ -0,0 +1,23 @@
<HomeScreenView>
md_bg_color: self.theme_cls.backgroundColor
main_container:main_container
MDBoxLayout:
NavRail:
screen:root
MDAnchorLayout:
anchor_y: 'top'
padding:"10dp"
MDBoxLayout:
orientation: 'vertical'
id:p
SearchBar:
MDScrollView:
size_hint:1,1
MDBoxLayout:
id:main_container
padding:"50dp","5dp","50dp","150dp"
spacing:"10dp"
orientation: 'vertical'
size_hint_y:None
height:max(self.minimum_height,p.height,1800)

View File

@@ -0,0 +1,7 @@
from kivy.properties import ObjectProperty
from View.base_screen import BaseScreenView
class HomeScreenView(BaseScreenView):
main_container = ObjectProperty()

View File

@@ -0,0 +1,28 @@
<MyListScreenView>
md_bg_color: self.theme_cls.backgroundColor
user_anime_list_container:user_anime_list_container
MDBoxLayout:
size_hint:1,1
NavRail:
screen:root
MDAnchorLayout:
anchor_y: 'top'
padding:"10dp"
size_hint:1,1
MDBoxLayout:
spacing:"40dp"
orientation: 'vertical'
size_hint:.95,1
SearchBar:
MDScrollView:
pos_hint:{"center_x":.5}
size_hint:1,1
MDGridLayout:
spacing: '40dp'
padding: "100dp","50dp","10dp","200dp"
id:user_anime_list_container
cols:4 if root.width<=1100 else 5
size_hint_y:None
height:self.minimum_height

View File

@@ -0,0 +1,21 @@
from kivy.properties import ObjectProperty, StringProperty, DictProperty
from kivy.clock import Clock
from View.base_screen import BaseScreenView
class MyListScreenView(BaseScreenView):
user_anime_list_container = ObjectProperty()
def model_is_changed(self) -> None:
"""
Called whenever any change has occurred in the data model.
The view in this method tracks these changes and updates the UI
according to these changes.
"""
def on_enter(self):
Clock.schedule_once(lambda _: self.controller.requested_update_my_list_screen())
def update_layout(self, widget):
self.user_anime_list_container.add_widget(widget)

View File

@@ -0,0 +1,3 @@
from .filters import Filters
from .pagination import SearchResultsPagination
from .trending_sidebar import TrendingAnimeSideBar

View File

@@ -0,0 +1,27 @@
<FilterDropDown>:
MDDropDownItemText:
text: root.text
<FilterLabel@MDLabel>:
adaptive_width:True
<Filters>:
adaptive_height:True
spacing:"10dp"
size_hint_x:.95
pos_hint:{"center_x":.5}
padding:"10dp"
md_bg_color:self.theme_cls.surfaceContainerLowColor
FilterLabel:
text:"Sort By"
FilterDropDown:
id:sort_filter
text:root.filters["sort"]
on_release: root.open_filter_menu(self,"sort")
FilterLabel:
text:"Status"
FilterDropDown:
id:status_filter
text:root.filters["status"]
on_release: root.open_filter_menu(self,"status")

View File

@@ -0,0 +1,83 @@
from kivy.properties import StringProperty, DictProperty
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.dropdownitem import MDDropDownItem
from kivymd.uix.menu import MDDropdownMenu
class FilterDropDown(MDDropDownItem):
text: str = StringProperty()
class Filters(MDBoxLayout):
filters: dict = DictProperty({"sort": "SEARCH_MATCH", "status": "FINISHED"})
def open_filter_menu(self, menu_item, filter_name):
items = []
match filter_name:
case "sort":
items = [
"ID",
"ID_DESC",
"TITLE_ROMANJI",
"TITLE_ROMANJI_DESC",
"TITLE_ENGLISH",
"TITLE_ENGLISH_DESC",
"TITLE_NATIVE",
"TITLE_NATIVE_DESC",
"TYPE",
"TYPE_DESC",
"FORMAT",
"FORMAT_DESC",
"START_DATE",
"START_DATE_DESC",
"END_DATE",
"END_DATE_DESC",
"SCORE",
"SCORE_DESC",
"TRENDING",
"TRENDING_DESC",
"EPISODES",
"EPISODES_DESC",
"DURATION",
"DURATION_DESC",
"STATUS",
"STATUS_DESC",
"UPDATED_AT",
"UPDATED_AT_DESC",
"SEARCH_MATCH",
"POPULARITY",
"POPULARITY_DESC",
"FAVOURITES",
"FAVOURITES_DESC",
]
case "status":
items = [
"FINISHED",
"RELEASING",
"NOT_YET_RELEASED",
"CANCELLED",
"HIATUS",
]
case _:
items = []
if items:
menu_items = [
{
"text": f"{item}",
"on_release": lambda filter_value=f"{item}": self.filter_menu_callback(
filter_name, filter_value
),
}
for item in items
]
MDDropdownMenu(caller=menu_item, items=menu_items).open()
def filter_menu_callback(self, filter_name, filter_value):
match filter_name:
case "sort":
self.ids.sort_filter.text = filter_value
self.filters["sort"] = filter_value
case "status":
self.ids.status_filter.text = filter_value
self.filters["status"] = filter_value

View File

@@ -0,0 +1,21 @@
<PaginationLabel@MDLabel>:
max_lines:0
shorten:False
adaptive_height:True
font_style: "Label"
pos_hint:{"center_y":.5}
halign:"center"
role: "medium"
<SearchResultsPagination>:
md_bg_color:self.theme_cls.surfaceContainerLowColor
radius:8
adaptive_height:True
MDIconButton:
icon:"arrow-left"
on_release:root.search_view.previous_page()
PaginationLabel:
text:"Page {} of {}".format(root.current_page,root.total_pages)
MDIconButton:
icon:"arrow-right"
on_release:root.search_view.next_page()

View File

@@ -0,0 +1,9 @@
from kivy.properties import ObjectProperty, NumericProperty
from kivymd.uix.boxlayout import MDBoxLayout
class SearchResultsPagination(MDBoxLayout):
current_page = NumericProperty()
total_pages = NumericProperty()
search_view = ObjectProperty()

View File

@@ -0,0 +1,6 @@
<TrendingAnimeSideBar>:
orientation: 'vertical'
adaptive_height:True
md_bg_color:self.theme_cls.surfaceContainerLowColor
pos_hint: {'center_x': 0.5}
padding:"25dp","25dp","25dp","200dp"

View File

@@ -0,0 +1,5 @@
from kivymd.uix.boxlayout import MDBoxLayout
class TrendingAnimeSideBar(MDBoxLayout):
pass

View File

@@ -0,0 +1,58 @@
<SearchScreenView>
md_bg_color: self.theme_cls.backgroundColor
search_results_container:search_results_container
trending_anime_sidebar:trending_anime_sidebar
search_results_pagination:search_results_pagination
filters:filters
MDBoxLayout:
size_hint:1,1
NavRail:
screen:root
MDAnchorLayout:
anchor_y: 'top'
padding:"10dp"
size_hint:1,1
MDBoxLayout:
orientation: 'vertical'
size_hint:1,1
SearchBar:
MDBoxLayout:
spacing:"20dp"
padding:"75dp","10dp","100dp","0dp"
MDBoxLayout:
orientation: 'vertical'
size_hint:1,1
Filters:
id:filters
MDBoxLayout:
spacing:"20dp"
MDScrollView:
size_hint:1,1
MDBoxLayout:
orientation: 'vertical'
size_hint_y:None
height:max(self.parent.parent.height,self.minimum_height)
MDGridLayout:
pos_hint: {'center_x': 0.5}
id:search_results_container
spacing: '40dp'
padding: "25dp","50dp","75dp","200dp"
cols:3 if root.width <= 1100 else 5
size_hint_y:None
height:max(self.parent.parent.height,self.minimum_height)
SearchResultsPagination:
id:search_results_pagination
search_view:root
MDBoxLayout:
orientation:"vertical"
size_hint_y:1
size_hint_x:None
width: dp(250)
HeaderLabel:
text:"Trending"
MDScrollView:
TrendingAnimeSideBar:
id:trending_anime_sidebar
height:max(self.parent.parent.height,self.minimum_height)

View File

@@ -0,0 +1,67 @@
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
from View.base_screen import BaseScreenView
from .components import TrendingAnimeSideBar, Filters, SearchResultsPagination
class SearchScreenView(BaseScreenView):
trending_anime_sidebar: TrendingAnimeSideBar = ObjectProperty()
search_results_pagination: SearchResultsPagination = ObjectProperty()
filters: Filters = ObjectProperty()
search_results_container = ObjectProperty()
search_term: str = StringProperty()
is_searching = False
has_next_page = False
current_page = 0
total_pages = 0
def handle_search_for_anime(self, search_widget=None, page=None):
if search_widget:
search_term = search_widget.text
elif page:
search_term = self.search_term
else:
return
if search_term and not (self.is_searching):
self.search_term = search_term
self.search_results_container.clear_widgets()
if filters := self.filters.filters:
Clock.schedule_once(
lambda _: self.controller.requested_search_for_anime(
search_term, **filters, page=page
)
)
else:
Clock.schedule_once(
lambda _: self.controller.requested_search_for_anime(
search_term, page=page
)
)
def update_layout(self, widget):
self.search_results_container.add_widget(widget)
def update_pagination(self, pagination_info):
self.search_results_pagination.current_page = self.current_page = (
pagination_info["currentPage"]
)
self.search_results_pagination.total_pages = self.total_pages = max(
int(pagination_info["total"] / 30), 1
)
self.has_next_page = pagination_info["hasNextPage"]
def next_page(self):
if self.has_next_page:
page = self.current_page + 1
self.handle_search_for_anime(page=page)
def previous_page(self):
if self.current_page > 1:
page = self.current_page - 1
self.handle_search_for_anime(page=page)
def update_trending_sidebar(self, trending_anime):
self.trending_anime_sidebar.add_widget(trending_anime)

View File

@@ -0,0 +1,8 @@
# screens
from .HomeScreen.home_screen import HomeScreenView
from .SearchScreen.search_screen import SearchScreenView
from .MylistScreen.my_list_screen import MyListScreenView
from .AnimeScreen.anime_screen import AnimeScreenView
from .CrashLogScreen.crashlog_screen import CrashLogScreenView
from .DownloadsScreen.download_screen import DownloadsScreenView
from .HelpScreen.help_screen import HelpScreenView

View File

@@ -0,0 +1,72 @@
from kivy.properties import ObjectProperty, StringProperty
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.navigationrail import MDNavigationRail, MDNavigationRailItem
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDIconButton
from kivymd.uix.tooltip import MDTooltip
from Utility.observer import Observer
class NavRail(MDNavigationRail):
screen = ObjectProperty()
class SearchBar(MDBoxLayout):
screen = ObjectProperty()
class Tooltip(MDTooltip):
pass
class TooltipMDIconButton(Tooltip, MDIconButton):
tooltip_text = StringProperty()
class CommonNavigationRailItem(MDNavigationRailItem):
icon = StringProperty()
text = StringProperty()
class BaseScreenView(MDScreen, Observer):
"""
A base class that implements a visual representation of the model data.
The view class must be inherited from this class.
"""
controller = ObjectProperty()
"""
Controller object - :class:`~Controller.controller_screen.ClassScreenControler`.
:attr:`controller` is an :class:`~kivy.properties.ObjectProperty`
and defaults to `None`.
"""
model = ObjectProperty()
"""
Model object - :class:`~Model.model_screen.ClassScreenModel`.
:attr:`model` is an :class:`~kivy.properties.ObjectProperty`
and defaults to `None`.
"""
manager_screens = ObjectProperty()
"""
Screen manager object - :class:`~kivymd.uix.screenmanager.MDScreenManager`.
:attr:`manager_screens` is an :class:`~kivy.properties.ObjectProperty`
and defaults to `None`.
"""
def __init__(self, **kw):
super().__init__(**kw)
# Often you need to get access to the application object from the view
# class. You can do this using this attribute.
from main import AniXStreamApp
self.app: AniXStreamApp = MDApp.get_running_app() # type: ignore
# Adding a view class as observer.
self.model.add_observer(self)

View File

@@ -0,0 +1 @@
from .media_card import MediaCard,MediaCardsContainer

View File

@@ -0,0 +1,4 @@
from kivy.uix.modalview import ModalView
class AnimdlDialogPopup(ModalView):
pass

View File

@@ -0,0 +1,3 @@
<MDLabel>:
allow_copy:True
allow_selection:True

View File

@@ -0,0 +1 @@
from .media_card import MediaCard,MediaCardsContainer

View File

@@ -0,0 +1,2 @@
from .media_player import MediaPopupVideoPlayer
from .media_popup import MediaPopup

View File

@@ -0,0 +1,19 @@
<MediaCardsContainer>
size_hint:1,None
height:max(self.minimum_height,dp(350),container.minimum_height)
container:container
orientation: 'vertical'
padding:"10dp"
spacing:"5dp"
MDLabel:
text:root.list_name
MDScrollView:
size_hint:1,None
height:container.minimum_height
MDBoxLayout:
id:container
spacing:"10dp"
padding:"0dp","10dp","100dp","10dp"
size_hint:None,None
height:self.minimum_height
width:self.minimum_width

View File

@@ -0,0 +1,10 @@
from kivy.uix.videoplayer import VideoPlayer
# TODO: make fullscreen exp better
class MediaPopupVideoPlayer(VideoPlayer):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def on_fullscreen(self, instance, value):
super().on_fullscreen(instance, value)
# self.state = "pause"

View File

@@ -0,0 +1,170 @@
#:import get_hex_from_color kivy.utils.get_hex_from_color
#:set yellow [.9,.9,0,.9]
<SingleLineLabel@MDLabel>:
shorten:True
shorten_from:"right"
adaptive_height:True
<PopupBoxLayout@MDBoxLayout>
adaptive_height:True
<Video>:
fit_mode:"fill"
# TODO: subdivide each main component to its own file
<MediaPopup>
size_hint: None, None
height: dp(530)
width: dp(400)
radius:[5,5,5,5]
md_bg_color:self.theme_cls.backgroundColor
anchor_y: 'top'
player:player
MDBoxLayout:
orientation: 'vertical'
MDRelativeLayout:
size_hint_y: None
height: dp(280)
line_color:root.caller.has_trailer_color
line_width:1
MediaPopupVideoPlayer:
id:player
source: root.caller.trailer_url
thumbnail:app.default_anime_image
state:"play" if root.caller.trailer_url else "stop"
# fit_mode:"fill"
size_hint_y: None
height: dp(280)
PopupBoxLayout:
padding: "10dp","5dp"
spacing:"5dp"
pos_hint: {'left': 1,'top': 1}
MDIcon:
icon: "star"
color:yellow
disabled: not(root.caller.stars[0])
MDIcon:
color:yellow
disabled: not(root.caller.stars[1])
icon: "star"
MDIcon:
color:yellow
disabled: not(root.caller.stars[2])
icon: "star"
MDIcon:
color:yellow
disabled: not(root.caller.stars[3])
icon: "star"
MDIcon:
color:yellow
icon: "star"
disabled: not(root.caller.stars[4])
MDIcon:
color: yellow
icon: "star"
disabled: not(root.caller.stars[5])
MDLabel:
text: f"{root.caller.episodes} Episodes"
halign:"right"
font_style:"Label"
role:"medium"
bold:True
pos_hint: {'center_y': 0.5}
adaptive_height:True
color: 0,0,0,.7
PopupBoxLayout:
padding:"5dp"
pos_hint: {'bottom': 1}
SingleLineLabel:
text:root.caller.media_status
opacity:.8
halign:"left"
font_style:"Label"
role:"medium"
bold:True
pos_hint: {'center_y': .5}
SingleLineLabel:
text:root.caller.first_aired_on
opacity:.8
halign:"right"
font_style:"Label"
role:"medium"
bold:True
pos_hint: {'center_y': .5}
# header
MDBoxLayout:
orientation: 'vertical'
padding:"10dp"
spacing:"10dp"
PopupBoxLayout:
PopupBoxLayout:
pos_hint: {'center_y': 0.5}
TooltipMDIconButton:
tooltip_text:root.caller.title
icon: "play-circle"
on_press:
root.dismiss()
app.show_anime_screen(root.caller.anime_id,root.caller.screen.name)
TooltipMDIconButton:
tooltip_text:"Add to your anime list"
icon: "plus-circle" if not(root.caller.is_in_my_list) else "check-circle"
on_release:
root.caller.is_in_my_list = not(root.caller.is_in_my_list)
self.icon = "plus-circle" if not(root.caller.is_in_my_list) else "check-circle"
TooltipMDIconButton:
disabled:True
tooltip_text:"Coming soon"
icon: "bell-circle" if not(root.caller.is_in_my_notify) else "bell-check"
PopupBoxLayout:
pos_hint: {'center_y': 0.5}
orientation: 'vertical'
SingleLineLabel:
font_style:"Label"
role:"small"
text: f"[color={get_hex_from_color(self.theme_cls.primaryColor)}]"+"Genres: "+"[/color]"+root.caller.genres
markup:True
PopupBoxLayout:
SingleLineLabel:
font_style:"Label"
role:"small"
markup:True
text: f"[color={get_hex_from_color(self.theme_cls.primaryColor)}]"+"Popularity: "+"[/color]"+root.caller.popularity
SingleLineLabel:
font_style:"Label"
markup:True
role:"small"
text: f"[color={get_hex_from_color(self.theme_cls.primaryColor)}]"+"Favourites: "+"[/color]"+root.caller.favourites
MDScrollView:
size_hint:1,1
do_scroll_y:True
MDLabel:
font_style:"Body"
role:"small"
text:root.caller.description
adaptive_height:True
# footer
PopupBoxLayout:
orientation:"vertical"
SingleLineLabel:
font_style:"Label"
markup:True
role:"small"
text: f"[color={get_hex_from_color(self.theme_cls.primaryColor)}]"+"Next Airing Episode: "+"[/color]"+root.caller.next_airing_episode
SingleLineLabel:
font_style:"Label"
role:"small"
markup:True
text: f"[color={get_hex_from_color(self.theme_cls.primaryColor)}]"+"Studios: " + "[/color]"+root.caller.studios
SingleLineLabel:
font_style:"Label"
markup:True
role:"small"
text: f"[color={get_hex_from_color(self.theme_cls.primaryColor)}]"+"Producers: " + "[/color]"+root.caller.producers
SingleLineLabel:
font_style:"Label"
markup:True
role:"small"
text: f"[color={get_hex_from_color(self.theme_cls.primaryColor)}]"+"Tags: "+"[/color]"+root.caller.tags

View File

@@ -0,0 +1,87 @@
from kivy.properties import ObjectProperty
from kivy.clock import Clock
from kivy.animation import Animation
from kivy.uix.modalview import ModalView
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import (
BackgroundColorBehavior,
StencilBehavior,
CommonElevationBehavior,
HoverBehavior,
)
class MediaPopup(
ThemableBehavior,
HoverBehavior,
StencilBehavior,
CommonElevationBehavior,
BackgroundColorBehavior,
ModalView,
):
caller = ObjectProperty()
player = ObjectProperty()
def __init__(self, caller, *args, **kwarg):
self.caller = caller
super(MediaPopup, self).__init__(*args, **kwarg)
self.player.bind(fullscreen=self.handle_clean_fullscreen_transition)
def open(self, *_args, **kwargs):
"""Display the modal in the Window.
When the view is opened, it will be faded in with an animation. If you
don't want the animation, use::
view.open(animation=False)
"""
from kivy.core.window import Window
if self._is_open:
return
self._window = Window
self._is_open = True
self.dispatch("on_pre_open")
Window.add_widget(self)
Window.bind(on_resize=self._align_center, on_keyboard=self._handle_keyboard)
self.center = self.caller.to_window(*self.caller.center)
self.fbind("center", self._align_center)
self.fbind("size", self._align_center)
if kwargs.get("animation", True):
ani = Animation(_anim_alpha=1.0, d=self._anim_duration)
ani.bind(on_complete=lambda *_args: self.dispatch("on_open"))
ani.start(self)
else:
self._anim_alpha = 1.0
self.dispatch("on_open")
def _align_center(self, *_args):
if self._is_open:
self.center = self.caller.to_window(*self.caller.center)
def on_leave(self, *args):
def _leave(dt):
self.player.state = "stop"
if self.player._video:
self.player._video.unload()
if not self.hovering:
self.dismiss()
Clock.schedule_once(_leave, 2)
def handle_clean_fullscreen_transition(self,instance,fullscreen):
if not fullscreen:
if not self._is_open:
instance.state = "stop"
if vid:=instance._video:
vid.unload()
else:
instance.state = "stop"
if vid:=instance._video:
vid.unload()
self.dismiss()

View File

@@ -0,0 +1,3 @@
<Tooltip>
MDTooltipPlain:
text:root.tooltip_text

View File

@@ -0,0 +1,24 @@
<MediaCard>
adaptive_height:True
spacing:"5dp"
image:"https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/bx163270-oxwgbe43Cpog.jpg"
size_hint_x: None
width:dp(100)
on_release:
self.open()
FitImage:
source:root.cover_image_url
fit_mode:"fill"
size_hint: None, None
width: dp(100)
height: dp(150)
MDDivider:
color:root.has_trailer_color
SingleLineLabel:
font_style:"Label"
role:"medium"
text:root.title
max_lines:2
halign:"center"
color:self.theme_cls.secondaryColor

View File

@@ -0,0 +1,96 @@
from kivy.properties import (
ObjectProperty,
StringProperty,
BooleanProperty,
ListProperty,
NumericProperty,
)
from kivy.clock import Clock
from kivy.uix.behaviors import ButtonBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.behaviors import HoverBehavior
from .components import MediaPopup
class MediaCard(ButtonBehavior, HoverBehavior, MDBoxLayout):
screen = ObjectProperty()
anime_id = NumericProperty()
title = StringProperty()
is_play = ObjectProperty()
trailer_url = StringProperty()
episodes = StringProperty()
favourites = StringProperty()
popularity = StringProperty()
media_status = StringProperty("Releasing")
is_in_my_list = BooleanProperty(False)
is_in_my_notify = BooleanProperty(False)
genres = StringProperty()
first_aired_on = StringProperty()
description = StringProperty()
producers = StringProperty()
studios = StringProperty()
next_airing_episode = StringProperty()
tags = StringProperty()
stars = ListProperty([0, 0, 0, 0, 0, 0])
cover_image_url = StringProperty()
preview_image = StringProperty()
has_trailer_color = ListProperty([.5, .5, .5, .5])
def __init__(self, trailer_url=None, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
if trailer_url:
self.trailer_url = trailer_url
self.adaptive_size = True
def on_enter(self):
def _open_popup(dt):
if self.hovering:
window = self.get_parent_window()
if window:
for widget in window.children: # type: ignore
if isinstance(widget, MediaPopup):
return
self.open()
Clock.schedule_once(_open_popup, 5)
def on_popup_open(self, popup: MediaPopup):
popup.center = self.center
def on_dismiss(self, popup: MediaPopup):
popup.player.state = "stop"
if popup.player._video:
popup.player._video.unload()
def set_preview_image(self, image):
self.preview_image = image
def set_trailer_url(self, trailer_url):
self.trailer_url = trailer_url
self.has_trailer_color = self.theme_cls.primaryColor
def open(self, *_):
popup = MediaPopup(self)
popup.title = self.title
popup.bind(on_dismiss=self.on_dismiss, on_open=self.on_popup_open)
popup.open(self)
# ---------------respond to user actions and call appropriate model-------------------------
def on_is_in_my_list(self, instance, in_user_anime_list):
if self.screen:
if in_user_anime_list:
self.screen.app.add_anime_to_user_anime_list(self.anime_id)
else:
self.screen.app.remove_anime_from_user_anime_list(self.anime_id)
def on_trailer_url(self, *args):
pass
class MediaCardsContainer(MDBoxLayout):
container = ObjectProperty()
list_name = StringProperty()

View File

@@ -0,0 +1,45 @@
<CommonNavigationRailItem>
MDNavigationRailItemIcon:
icon:root.icon
MDNavigationRailItemLabel:
text: root.text
<NavRail>:
anchor:"top"
type: "labeled"
md_bg_color: self.theme_cls.secondaryContainerColor
MDNavigationRailFabButton:
icon: "home"
on_press:
root.screen.manager_screens.current = "home screen"
CommonNavigationRailItem:
icon: "magnify"
text: "Search"
on_press:
root.screen.manager_screens.current = "search screen"
CommonNavigationRailItem:
icon: "bookmark"
text: "MyList"
on_press:
root.screen.manager_screens.current = "my list screen"
CommonNavigationRailItem:
icon: "download-circle"
text: "Downloads"
on_press:
root.screen.manager_screens.current = "downloads screen"
CommonNavigationRailItem:
icon: "cog"
text: "settings"
on_press:app.open_settings()
CommonNavigationRailItem:
icon: "help-circle"
text: "Help"
on_press:
root.screen.manager_screens.current = "help screen"
CommonNavigationRailItem:
icon: "bug"
text: "debug"
on_press:
root.screen.manager_screens.current = "crashlog screen"

View File

@@ -0,0 +1,3 @@
<Tooltip>
MDTooltipPlain:
text:root.tooltip_text

View File

@@ -0,0 +1,18 @@
<SearchBar>:
pos_hint: {'center_x': 0.5,'top': 1}
padding: "10dp"
adaptive_height:True
size_hint_x:.75
spacing: '20dp'
MDTextField:
size_hint_x:1
required:True
on_text_validate:
app.search_for_anime(args[0])
MDTextFieldLeadingIcon:
icon: "magnify"
MDTextFieldHintText:
text: "Search for anime"
MDIconButton:
pos_hint: {'center_y': 0.5}
icon: "account-circle"

View File

@@ -0,0 +1,50 @@
from Controller import (
SearchScreenController,
HomeScreenController,
MyListScreenController,
AnimeScreenController,
DownloadsScreenController,
HelpScreenController,
CrashLogScreenController,
)
from Model import (
HomeScreenModel,
SearchScreenModel,
MyListScreenModel,
AnimeScreenModel,
DownloadsScreenModel,
HelpScreenModel,
CrashLogScreenModel,
)
screens = {
"home screen": {
"model": HomeScreenModel,
"controller": HomeScreenController,
},
"search screen": {
"model": SearchScreenModel,
"controller": SearchScreenController,
},
"my list screen": {
"model": MyListScreenModel,
"controller": MyListScreenController,
},
"anime screen": {
"model": AnimeScreenModel,
"controller": AnimeScreenController,
},
"crashlog screen": {
"model": CrashLogScreenModel,
"controller": CrashLogScreenController,
},
"downloads screen": {
"model": DownloadsScreenModel,
"controller": DownloadsScreenController,
},
"help screen": {
"model": HelpScreenModel,
"controller": HelpScreenController,
},
}