mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-12 07:40:41 -08:00
feat(anime screen, search screen): finish basic ui for anime screen with ep selection and update search screen to use recycle view
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
*.ass
|
||||
vids
|
||||
data/
|
||||
.project/
|
||||
crashdump.txt
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
||||
@@ -1,14 +1,44 @@
|
||||
# Changes
|
||||
|
||||
# # only load trailers when needed
|
||||
-# # only load trailers when needed
|
||||
|
||||
- change how media card loader class works
|
||||
- create function to get a trailer url and cache it for 360 secs
|
||||
- refactor the codebase to reflect proposed changes
|
||||
|
||||
# # # change how media card loader class works
|
||||
-# # # change how media card loader class works
|
||||
|
||||
- remove unnecesarry code
|
||||
- make the media card function to still work so it doesn't cause a break in existing functionality- add a get trailer function which takes a callback
|
||||
|
||||
# # Use one media popup for all the media cards
|
||||
# - # Use one media popup for all the media cards
|
||||
|
||||
# 9 june
|
||||
|
||||
# # search screen
|
||||
|
||||
change the search results layout to be a recycle grid layout
|
||||
|
||||
change the trending bar to be a recyle box layout
|
||||
|
||||
AFFECTED:
|
||||
|
||||
- search results
|
||||
- trendig bar
|
||||
|
||||
# # anime screen
|
||||
|
||||
# # # Features
|
||||
|
||||
- video player
|
||||
- controls
|
||||
- next button + previous button
|
||||
- auto next
|
||||
- refresh button
|
||||
- play in mpv button
|
||||
- episodes bar
|
||||
- servers bar
|
||||
|
||||
NOTE:
|
||||
the affected:
|
||||
anime screen model, view, controller
|
||||
|
||||
@@ -21,6 +21,9 @@ class AnimeScreenController:
|
||||
anime_title
|
||||
)
|
||||
self.view.current_links = self.model.get_episode_streams(episode)
|
||||
# TODO: add auto start
|
||||
#
|
||||
# self.view.current_link = self.view.current_links[0]["gogoanime"][0]
|
||||
|
||||
def update_anime_view(self, id, title, caller_screen_name):
|
||||
self.fetch_streams(title)
|
||||
|
||||
@@ -119,9 +119,9 @@ class HomeScreenController:
|
||||
self.populate_errors = []
|
||||
Clock.schedule_once(lambda _: self.trending_anime(), 1)
|
||||
Clock.schedule_once(lambda _: self.highest_scored_anime(), 2)
|
||||
Clock.schedule_once(lambda _: self.popular_anime(), 3)
|
||||
Clock.schedule_once(lambda _: self.recently_updated_anime(), 5)
|
||||
# Clock.schedule_once(lambda _: self.popular_anime(), 3)
|
||||
# Clock.schedule_once(lambda _: self.favourite_anime(), 4)
|
||||
# Clock.schedule_once(lambda _: self.recently_updated_anime(), 5)
|
||||
# Clock.schedule_once(lambda _: self.upcoming_anime(), 6)
|
||||
|
||||
if self.populate_errors:
|
||||
|
||||
@@ -21,10 +21,10 @@ class SearchScreenController:
|
||||
"""Gets and adds the trending anime to the search screen"""
|
||||
trending_cards_generator = self.model.get_trending_anime()
|
||||
if isgenerator(trending_cards_generator):
|
||||
self.view.trending_anime_sidebar.clear_widgets()
|
||||
# self.view.trending_anime_sidebar.data = []
|
||||
for card in trending_cards_generator:
|
||||
card.screen = self.view
|
||||
card.pos_hint = {"center_x": 0.5}
|
||||
card["screen"] = self.view
|
||||
# card["pos_hint"] = {"center_x": 0.5}
|
||||
self.view.update_trending_sidebar(card)
|
||||
else:
|
||||
Logger.error("Home Screen:Failed to load trending anime")
|
||||
@@ -34,7 +34,7 @@ class SearchScreenController:
|
||||
search_Results = self.model.search_for_anime(anime_title, **kwargs)
|
||||
if isgenerator(search_Results):
|
||||
for result_card in search_Results:
|
||||
result_card.screen = self.view
|
||||
result_card["screen"] = self.view
|
||||
self.view.update_layout(result_card)
|
||||
Clock.schedule_once(
|
||||
lambda _: self.view.update_pagination(self.model.pagination_info)
|
||||
|
||||
@@ -6,7 +6,7 @@ from fuzzywuzzy import fuzz
|
||||
|
||||
|
||||
def anime_title_percentage_match(
|
||||
possible_user_requested_anime_title: str, title: str
|
||||
possible_user_requested_anime_title: str, title: tuple
|
||||
) -> int:
|
||||
"""Returns the percentage match between the possible title and user title
|
||||
|
||||
@@ -19,7 +19,10 @@ def anime_title_percentage_match(
|
||||
"""
|
||||
print(locals())
|
||||
|
||||
percentage_ratio = fuzz.ratio(title, possible_user_requested_anime_title)
|
||||
percentage_ratio = max(
|
||||
fuzz.ratio(title[0].lower(), possible_user_requested_anime_title.lower()),
|
||||
fuzz.ratio(title[1].lower(), possible_user_requested_anime_title.lower()),
|
||||
)
|
||||
print(percentage_ratio)
|
||||
return percentage_ratio
|
||||
|
||||
@@ -36,11 +39,11 @@ class AnimeScreenModel(BaseScreenModel):
|
||||
current_anime_id = "0"
|
||||
current_title = ""
|
||||
|
||||
def get_anime_data_from_provider(self, anime_title: str, id=None):
|
||||
def get_anime_data_from_provider(self, anime_title: tuple, id=None):
|
||||
if self.current_title == anime_title and self.current_anime_data:
|
||||
return self.current_anime_data
|
||||
|
||||
search_results = anime_provider.search_for_anime(anime_title)
|
||||
search_results = anime_provider.search_for_anime(anime_title[0])
|
||||
|
||||
if search_results:
|
||||
_search_results = search_results["shows"]["edges"]
|
||||
|
||||
@@ -16,13 +16,22 @@ class MediaCardDataLoader(object):
|
||||
anime_item: AnilistBaseMediaDataSchema,
|
||||
):
|
||||
media_card_data = {}
|
||||
media_card_data["viewclass"] = "MediaCard"
|
||||
media_card_data["anime_id"] = anime_id = anime_item["id"]
|
||||
|
||||
# TODO: ADD language preference
|
||||
if anime_item["title"].get("romaji"):
|
||||
media_card_data["title"] = anime_item["title"]["romaji"]
|
||||
media_card_data["_title"] = (
|
||||
anime_item["title"]["romaji"],
|
||||
str(anime_item["title"]["english"]),
|
||||
)
|
||||
else:
|
||||
media_card_data["title"] = anime_item["title"]["english"]
|
||||
media_card_data["_title"] = (
|
||||
anime_item["title"]["english"],
|
||||
str(anime_item["title"]["romaji"]),
|
||||
)
|
||||
|
||||
media_card_data["cover_image_url"] = anime_item["coverImage"]["medium"]
|
||||
|
||||
|
||||
@@ -6,49 +6,78 @@
|
||||
adaptive_height:True
|
||||
bold:True
|
||||
|
||||
<EpisodeButton>:
|
||||
pos_hint:{"center_y":0.5,"center_x":0.5}
|
||||
on_press:root.change_episode_callback(root.text)
|
||||
radius: 10
|
||||
MDButtonText:
|
||||
text:root.text
|
||||
|
||||
|
||||
|
||||
<AnimeScreenView>:
|
||||
md_bg_color: self.theme_cls.backgroundColor
|
||||
episodes_container:episodes_container
|
||||
MDBoxLayout:
|
||||
padding:"10dp"
|
||||
orientation: 'vertical'
|
||||
MDBoxLayout:
|
||||
adaptive_height:True
|
||||
MDIconButton:
|
||||
icon:"arrow-left"
|
||||
on_press:root.manager_screens.current = root.caller_screen_name
|
||||
MDTextField:
|
||||
on_text_validate:
|
||||
root.update_current_link(self)
|
||||
|
||||
VideoPlayer:
|
||||
source:root.current_link
|
||||
|
||||
MDBoxLayout:
|
||||
VideoPlayer:
|
||||
source:root.current_link
|
||||
AnimeBoxLayout:
|
||||
AnimeLabel:
|
||||
text:"Sub"
|
||||
MDSegmentedButton:
|
||||
id:pl
|
||||
multiselect:False
|
||||
MDSegmentedButtonItem:
|
||||
on_active:
|
||||
pl.selected_segments = [self]
|
||||
root.update_current_video_stream("gogoanime")
|
||||
MDSegmentButtonLabel:
|
||||
text:"GoGoAnime"
|
||||
MDSegmentedButtonItem:
|
||||
on_active:
|
||||
root.update_current_video_stream("dropbox")
|
||||
pl.selected_segments = [self]
|
||||
MDSegmentButtonLabel:
|
||||
text:"DropBox"
|
||||
MDSegmentedButtonItem:
|
||||
on_active:
|
||||
root.update_current_video_stream("sharepoint")
|
||||
pl.selected_segments = [self]
|
||||
MDSegmentButtonLabel:
|
||||
text:"Share Point"
|
||||
MDSegmentedButtonItem:
|
||||
on_active:
|
||||
root.update_current_video_stream("wetransfer")
|
||||
pl.selected_segments = [self]
|
||||
MDSegmentButtonLabel:
|
||||
text:"weTransfer"
|
||||
padding: "20dp"
|
||||
radius:5
|
||||
spacing:"10dp"
|
||||
md_bg_color: self.theme_cls.surfaceContainerLowColor
|
||||
AnimeBoxLayout:
|
||||
AnimeLabel:
|
||||
text:"Sub servers: "
|
||||
MDSegmentedButton:
|
||||
id:pl
|
||||
multiselect:False
|
||||
MDSegmentedButtonItem:
|
||||
on_active:
|
||||
pl.selected_segments = [self]
|
||||
root.update_current_video_stream("gogoanime")
|
||||
MDSegmentButtonLabel:
|
||||
text:"GoGoAnime"
|
||||
MDSegmentedButtonItem:
|
||||
on_active:
|
||||
root.update_current_video_stream("dropbox")
|
||||
pl.selected_segments = [self]
|
||||
MDSegmentButtonLabel:
|
||||
text:"DropBox"
|
||||
MDSegmentedButtonItem:
|
||||
on_active:
|
||||
root.update_current_video_stream("sharepoint")
|
||||
pl.selected_segments = [self]
|
||||
MDSegmentButtonLabel:
|
||||
text:"Share Point"
|
||||
MDSegmentedButtonItem:
|
||||
on_active:
|
||||
root.update_current_video_stream("wetransfer")
|
||||
pl.selected_segments = [self]
|
||||
MDSegmentButtonLabel:
|
||||
text:"weTransfer"
|
||||
MDDivider:
|
||||
|
||||
MDRecycleView:
|
||||
id: episodes_container
|
||||
size_hint_y:None
|
||||
height:"50dp"
|
||||
key_viewclass:"viewclass"
|
||||
RecycleBoxLayout:
|
||||
size_hint: None,1
|
||||
key_size:"width"
|
||||
spacing:"10dp"
|
||||
width:self.minimum_width
|
||||
default_size_hint:0,0
|
||||
default_size:30,30
|
||||
default_pos_hint:{"center_y":0.5,"center_x":0.5}
|
||||
|
||||
|
||||
@@ -1,33 +1,60 @@
|
||||
from kivy.properties import ListProperty, ObjectProperty, StringProperty
|
||||
|
||||
from kivy.uix.widget import Factory
|
||||
from kivymd.uix.button import MDButton
|
||||
|
||||
from ...libs.anilist import AnilistBaseMediaDataSchema
|
||||
from ...View.base_screen import BaseScreenView
|
||||
|
||||
|
||||
class EpisodeButton(MDButton):
|
||||
text = StringProperty()
|
||||
change_episode_callback = ObjectProperty()
|
||||
|
||||
|
||||
Factory.register("EpisodeButton", cls=EpisodeButton)
|
||||
|
||||
|
||||
class AnimeScreenView(BaseScreenView):
|
||||
"""The anime screen view"""
|
||||
|
||||
current_link = StringProperty(
|
||||
"https://uc951f724c20bbec8df447bac605.dl.dropboxusercontent.com/cd/0/get/CUdx6k2qw-zqY86ftfFHqkmPqGuVrfjpE68B_EkcvZXcZLnjim_ZTHd-qNVb_mEbos9UsuhY8FJGdgf86RUZ-IJqZtz3tt8_CUVTloQAeZ47HtNiKjQ0ESvYdLuwqDjqwK2rNfsfiZI2cXBaKiUyJtljEeRL8whSff2wA9Z4tX1cow/file"
|
||||
)
|
||||
current_link = StringProperty()
|
||||
current_links = ListProperty([])
|
||||
current_anime_data = ObjectProperty()
|
||||
caller_screen_name = ObjectProperty()
|
||||
current_title = StringProperty()
|
||||
current_title = ()
|
||||
episodes_container = ObjectProperty()
|
||||
|
||||
def update_layout(self, data: AnilistBaseMediaDataSchema, caller_screen_name: str):
|
||||
return
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
# self.update_episodes(100)
|
||||
|
||||
def update_episodes(self, episodes_list):
|
||||
self.episodes_container.data = []
|
||||
for episode in episodes_list:
|
||||
self.episodes_container.data.append(
|
||||
{
|
||||
"viewclass": "EpisodeButton",
|
||||
"text": str(episode),
|
||||
"change_episode_callback": lambda x=episode: self.update_current_episode(
|
||||
x
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
def on_current_anime_data(self, instance, value):
|
||||
data = value["show"]
|
||||
self.update_episodes(data["availableEpisodesDetail"]["sub"][::-1])
|
||||
|
||||
def update_current_episode(self, episode):
|
||||
self.controller.fetch_streams(self.current_title, episode)
|
||||
# self.current_link = self.current_links[0]["gogoanime"][0]
|
||||
|
||||
def update_current_video_stream(self, server, is_dub=False):
|
||||
for link in self.current_links:
|
||||
if stream_link := link.get(server):
|
||||
print(link)
|
||||
self.current_link = stream_link[0]
|
||||
break
|
||||
# print(link)
|
||||
|
||||
def update_current_link(self, field):
|
||||
self.controller.fetch_streams(self.current_title, field.text)
|
||||
|
||||
def add_to_user_anime_list(self, *args):
|
||||
self.app.add_anime_to_user_anime_list(self.model.anime_id)
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
<TrendingAnimeSideBar>:
|
||||
orientation: 'vertical'
|
||||
adaptive_height:True
|
||||
md_bg_color:self.theme_cls.surfaceContainerLowColor
|
||||
padding:"25dp","25dp","25dp","200dp"
|
||||
pos_hint: {'center_x': 0.5}
|
||||
padding:"25dp","25dp","25dp","200dp"
|
||||
key_viewclass:"viewclass"
|
||||
size_hint: None,1
|
||||
width:"250dp"
|
||||
pos_hint: {'center_x':.5}
|
||||
RecycleBoxLayout:
|
||||
orientation: 'vertical'
|
||||
key_size:"height"
|
||||
key_viewclass:"viewclass"
|
||||
pos_hint: {'center_x': 0.8}
|
||||
size_hint:None,None
|
||||
default_size_hint:None, None
|
||||
default_pos_hint:{"center_x":0.8}
|
||||
default_size:dp(150),dp(100)
|
||||
width:"250dp"
|
||||
spacing:"10dp"
|
||||
height:max(self.minimum_height,500)
|
||||
#padding:"0dp","10dp","100dp","10dp"
|
||||
# height:max(self.parent.parent.height,self.minimum_height+100)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.recycleview import MDRecycleView
|
||||
|
||||
|
||||
class TrendingAnimeSideBar(MDBoxLayout):
|
||||
class TrendingAnimeSideBar(MDRecycleView):
|
||||
pass
|
||||
|
||||
@@ -33,17 +33,22 @@
|
||||
orientation: 'vertical'
|
||||
size_hint_y:None
|
||||
height:max(self.parent.parent.height,self.minimum_height)
|
||||
MDGridLayout:
|
||||
pos_hint: {'center_x': 0.5}
|
||||
MDRecycleView:
|
||||
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)
|
||||
key_viewclass:"viewclass"
|
||||
MDRecycleGridLayout:
|
||||
pos_hint: {'center_x': 0.5}
|
||||
spacing: '40dp'
|
||||
padding: "25dp","50dp","75dp","200dp"
|
||||
default_size_hint:None,None
|
||||
default_size:dp(100),dp(150)
|
||||
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
|
||||
@@ -51,8 +56,5 @@
|
||||
width: dp(250)
|
||||
HeaderLabel:
|
||||
text:"Trending"
|
||||
MDScrollView:
|
||||
TrendingAnimeSideBar:
|
||||
id:trending_anime_sidebar
|
||||
height:max(self.parent.parent.height,self.minimum_height)
|
||||
|
||||
TrendingAnimeSideBar:
|
||||
id:trending_anime_sidebar
|
||||
|
||||
@@ -28,7 +28,7 @@ class SearchScreenView(BaseScreenView):
|
||||
|
||||
if search_term and not (self.is_searching):
|
||||
self.search_term = search_term
|
||||
self.search_results_container.clear_widgets()
|
||||
self.search_results_container.data = []
|
||||
if filters := self.filters.filters:
|
||||
Clock.schedule_once(
|
||||
lambda _: self.controller.requested_search_for_anime(
|
||||
@@ -43,7 +43,7 @@ class SearchScreenView(BaseScreenView):
|
||||
)
|
||||
|
||||
def update_layout(self, widget):
|
||||
self.search_results_container.add_widget(widget)
|
||||
self.search_results_container.data.append(widget)
|
||||
|
||||
def update_pagination(self, pagination_info):
|
||||
self.search_results_pagination.current_page = self.current_page = (
|
||||
@@ -65,4 +65,4 @@ class SearchScreenView(BaseScreenView):
|
||||
self.handle_search_for_anime(page=page)
|
||||
|
||||
def update_trending_sidebar(self, trending_anime):
|
||||
self.trending_anime_sidebar.add_widget(trending_anime)
|
||||
self.trending_anime_sidebar.data.append(trending_anime)
|
||||
|
||||
@@ -9,15 +9,14 @@
|
||||
bold:True
|
||||
adaptive_height:True
|
||||
text:root.list_name
|
||||
MDScrollView:
|
||||
MDRecycleView:
|
||||
id:container
|
||||
key_viewclass:"viewclass"
|
||||
key_size:"width"
|
||||
RecycleBoxLayout:
|
||||
size_hint:None,1
|
||||
width:self.minimum_width
|
||||
default_size_hint:None, None
|
||||
default_size:dp(150),dp(100)
|
||||
spacing:"10dp"
|
||||
padding:"0dp","10dp","100dp","10dp"
|
||||
MDRecycleView:
|
||||
id:container
|
||||
key_viewclass:"viewclass"
|
||||
key_size:"width"
|
||||
RecycleBoxLayout:
|
||||
size_hint:None,1
|
||||
width:self.minimum_width
|
||||
default_size_hint:None, None
|
||||
default_size:dp(150),dp(100)
|
||||
spacing:"10dp"
|
||||
padding:"0dp","10dp","100dp","10dp"
|
||||
|
||||
@@ -30,9 +30,11 @@
|
||||
# TODO: Remove the test source
|
||||
MediaPopupVideoPlayer:
|
||||
id:player
|
||||
source: root.caller.trailer_url if root.caller.trailer_url else 'https://www088.vipanicdn.net/streamhls/abae70787c7bd2fcd4fab986c2a5aeba/ep.7.1703900604.m3u8'
|
||||
source: root.caller.trailer_url #if root.caller.trailer_url else 'https://www088.vipanicdn.net/streamhls/abae70787c7bd2fcd4fab986c2a5aeba/ep.7.1703900604.m3u8'
|
||||
thumbnail:app.default_anime_image
|
||||
state:"play" if root.caller.trailer_url else "stop"
|
||||
#state:"play" if root.caller.trailer_url else "stop"
|
||||
on_state:
|
||||
root.caller._get_trailer()
|
||||
# fit_mode:"fill"
|
||||
size_hint_y: None
|
||||
height: dp(280)
|
||||
@@ -103,11 +105,12 @@
|
||||
PopupBoxLayout:
|
||||
pos_hint: {'center_y': 0.5}
|
||||
TooltipMDIconButton:
|
||||
tooltip_text:root.caller.title
|
||||
tooltip_text:"Play"
|
||||
|
||||
icon: "play-circle"
|
||||
on_press:
|
||||
root.dismiss()
|
||||
app.show_anime_screen(root.caller.anime_id,root.caller.title,root.caller.screen.name)
|
||||
app.show_anime_screen(root.caller.anime_id,root.caller._title,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"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
spacing:"5dp"
|
||||
size_hint_x: None
|
||||
width:dp(100)
|
||||
height: dp(150)
|
||||
FitImage:
|
||||
source:root.cover_image_url
|
||||
fit_mode:"fill"
|
||||
|
||||
@@ -39,6 +39,7 @@ class MediaCard(HoverBehavior, MDBoxLayout):
|
||||
preview_image = StringProperty()
|
||||
has_trailer_color = ListProperty([0.5, 0.5, 0.5, 0.5])
|
||||
_popup_opened = False
|
||||
_title = ()
|
||||
|
||||
def __init__(self, trailer_url=None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -118,19 +119,19 @@ class MediaCard(HoverBehavior, MDBoxLayout):
|
||||
popup.bind(on_dismiss=self.on_dismiss, on_open=self.on_popup_open)
|
||||
popup.open(self)
|
||||
|
||||
def _get_trailer(dt):
|
||||
if trailer := self._trailer_url:
|
||||
# trailer stuff
|
||||
from ....Utility.media_card_loader import media_card_loader
|
||||
def _get_trailer(self):
|
||||
if self.trailer_url:
|
||||
return
|
||||
if trailer := self._trailer_url:
|
||||
# trailer stuff
|
||||
from ....Utility.media_card_loader import media_card_loader
|
||||
|
||||
if trailer_url := media_card_loader.get_trailer_from_pytube(
|
||||
trailer, self.title
|
||||
):
|
||||
self.trailer_url = trailer_url
|
||||
else:
|
||||
self._trailer_url = ""
|
||||
|
||||
Clock.schedule_once(_get_trailer, 1)
|
||||
if trailer_url := media_card_loader.get_trailer_from_pytube(
|
||||
trailer, self.title
|
||||
):
|
||||
self.trailer_url = trailer_url
|
||||
else:
|
||||
self._trailer_url = ""
|
||||
|
||||
# ---------------respond to user actions and call appropriate model-------------------------
|
||||
def on_is_in_my_list(self, instance, in_user_anime_list):
|
||||
|
||||
@@ -26,14 +26,14 @@ screens = {
|
||||
"model": SearchScreenModel,
|
||||
"controller": SearchScreenController,
|
||||
},
|
||||
"my list screen": {
|
||||
"model": MyListScreenModel,
|
||||
"controller": MyListScreenController,
|
||||
},
|
||||
"anime screen": {
|
||||
"model": AnimeScreenModel,
|
||||
"controller": AnimeScreenController,
|
||||
},
|
||||
"my list screen": {
|
||||
"model": MyListScreenModel,
|
||||
"controller": MyListScreenController,
|
||||
},
|
||||
"crashlog screen": {
|
||||
"model": CrashLogScreenModel,
|
||||
"controller": CrashLogScreenController,
|
||||
|
||||
Reference in New Issue
Block a user