2015-07-02 10:37:13 +00:00
// Copyright (c) 2014- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
2016-10-12 09:32:24 +00:00
# include <functional>
2020-10-04 18:48:47 +00:00
# include "Common/UI/Screen.h"
# include "Common/UI/Context.h"
# include "Common/UI/ViewGroup.h"
2023-06-18 10:13:40 +00:00
# include "Common/UI/IconCache.h"
2020-10-04 21:24:14 +00:00
# include "Common/Render/DrawBuffer.h"
2013-11-20 13:42:48 +00:00
# include "Common/Log.h"
2020-10-04 07:29:36 +00:00
# include "Common/Data/Text/I18n.h"
# include "Common/Data/Format/JSONReader.h"
2015-07-02 10:37:13 +00:00
# include "Common/StringUtils.h"
2022-11-21 19:15:22 +00:00
# include "Common/Render/ManagedTexture.h"
2023-06-18 10:13:40 +00:00
# include "Common/Net/NetBuffer.h"
2013-11-20 13:42:48 +00:00
# include "Core/Config.h"
2015-07-04 16:05:17 +00:00
# include "Core/System.h"
2016-03-06 21:16:50 +00:00
# include "Core/Util/GameManager.h"
2015-07-04 16:05:17 +00:00
# include "UI/EmuScreen.h"
2013-11-20 13:42:48 +00:00
# include "UI/Store.h"
2023-07-21 08:19:16 +00:00
const char * storeBaseUrlHttp = " http://store.ppsspp.org/ " ;
const char * storeBaseUrlHttps = " https://store.ppsspp.org/ " ;
static std : : string StoreBaseUrl ( ) {
return System_GetPropertyBool ( SYSPROP_SUPPORTS_HTTPS ) ? storeBaseUrlHttps : storeBaseUrlHttp ;
}
2013-11-20 13:42:48 +00:00
2015-07-02 10:37:13 +00:00
// baseUrl is assumed to have a trailing slash, and not contain any subdirectories.
2023-12-14 11:23:31 +00:00
std : : string ResolveUrl ( const std : : string & baseUrl , const std : : string & url ) {
2015-07-02 10:37:13 +00:00
if ( url . empty ( ) ) {
return baseUrl ;
} else if ( url [ 0 ] = = ' / ' ) {
return baseUrl + url . substr ( 1 ) ;
2019-10-06 15:30:39 +00:00
} else if ( startsWith ( url , " http:// " ) | | startsWith ( url , " https:// " ) ) {
2015-07-02 10:37:13 +00:00
return url ;
} else {
// Huh.
return baseUrl + url ;
}
}
class HttpImageFileView : public UI : : View {
public :
2023-07-20 09:25:27 +00:00
HttpImageFileView ( http : : RequestManager * requestManager , const std : : string & path , UI : : ImageSizeMode sizeMode = UI : : IS_DEFAULT , bool useIconCache = true , UI : : LayoutParams * layoutParams = nullptr )
: UI : : View ( layoutParams ) , path_ ( path ) , sizeMode_ ( sizeMode ) , requestManager_ ( requestManager ) , useIconCache_ ( useIconCache ) {
2023-06-18 10:13:40 +00:00
2023-06-18 12:54:01 +00:00
if ( useIconCache & & g_iconCache . MarkPending ( path_ ) ) {
2023-06-18 10:13:40 +00:00
const char * acceptMime = " image/png, image/jpeg, image/*; q=0.9, */*; q=0.8 " ;
2023-09-20 19:45:03 +00:00
requestManager_ - > StartDownloadWithCallback ( path_ , Path ( ) , http : : ProgressBarMode : : DELAYED , [ ] ( http : : Request & download ) {
// Can't touch 'this' in this function! Don't use captures!
std : : string path = download . url ( ) ;
2023-06-18 12:54:01 +00:00
if ( download . ResultCode ( ) = = 200 ) {
std : : string data ;
download . buffer ( ) . TakeAll ( & data ) ;
2023-07-20 17:10:24 +00:00
if ( ! data . empty ( ) ) {
2023-09-20 19:45:03 +00:00
g_iconCache . InsertIcon ( path , IconFormat : : PNG , std : : move ( data ) ) ;
2023-07-20 17:10:24 +00:00
} else {
2023-09-20 19:45:03 +00:00
g_iconCache . CancelPending ( path ) ;
2023-07-20 17:10:24 +00:00
}
2023-06-18 12:54:01 +00:00
} else {
2023-09-20 19:45:03 +00:00
g_iconCache . CancelPending ( path ) ;
2023-06-18 12:54:01 +00:00
}
2023-06-18 10:13:40 +00:00
} , acceptMime ) ;
}
}
2015-07-02 10:37:13 +00:00
~ HttpImageFileView ( ) {
2023-09-20 19:45:03 +00:00
if ( download_ ) {
2015-10-06 17:07:09 +00:00
download_ - > Cancel ( ) ;
2023-09-20 19:45:03 +00:00
}
2015-07-02 10:37:13 +00:00
}
void GetContentDimensions ( const UIContext & dc , float & w , float & h ) const override ;
void Draw ( UIContext & dc ) override ;
2021-02-22 00:38:02 +00:00
std : : string DescribeText ( ) const override { return " " ; }
2015-07-02 10:37:13 +00:00
2023-12-14 11:23:31 +00:00
void SetFilename ( const std : : string & filename ) ;
2015-07-02 10:37:13 +00:00
void SetColor ( uint32_t color ) { color_ = color ; }
void SetFixedSize ( float fixW , float fixH ) { fixedSizeW_ = fixW ; fixedSizeH_ = fixH ; }
void SetCanBeFocused ( bool can ) { canFocus_ = can ; }
bool CanBeFocused ( ) const override { return false ; }
const std : : string & GetFilename ( ) const { return path_ ; }
private :
2023-07-21 20:04:05 +00:00
void DownloadCompletedCallback ( http : : Request & download ) ;
2015-07-02 10:37:13 +00:00
2017-05-18 10:52:03 +00:00
bool canFocus_ = false ;
2023-06-18 10:13:40 +00:00
bool useIconCache_ = false ;
std : : string path_ ; // or cache key
2017-05-18 10:52:03 +00:00
uint32_t color_ = 0xFFFFFFFF ;
2015-07-02 10:37:13 +00:00
UI : : ImageSizeMode sizeMode_ ;
2023-07-20 09:25:27 +00:00
http : : RequestManager * requestManager_ ;
2023-07-21 20:04:05 +00:00
std : : shared_ptr < http : : Request > download_ ;
2015-07-02 10:37:13 +00:00
std : : string textureData_ ;
2023-12-12 21:44:03 +00:00
Draw : : AutoRef < Draw : : Texture > texture_ ;
2017-05-18 10:52:03 +00:00
bool textureFailed_ = false ;
float fixedSizeW_ = 0.0f ;
float fixedSizeH_ = 0.0f ;
2015-07-02 10:37:13 +00:00
} ;
void HttpImageFileView : : GetContentDimensions ( const UIContext & dc , float & w , float & h ) const {
switch ( sizeMode_ ) {
case UI : : IS_FIXED :
w = fixedSizeW_ ;
h = fixedSizeH_ ;
break ;
case UI : : IS_DEFAULT :
default :
2023-06-18 10:13:40 +00:00
if ( useIconCache_ ) {
int width , height ;
if ( g_iconCache . GetDimensions ( path_ , & width , & height ) ) {
w = width ;
h = height ;
} else {
w = 16 ;
h = 16 ;
}
2015-07-02 10:37:13 +00:00
} else {
2023-06-18 10:13:40 +00:00
if ( texture_ ) {
float texw = ( float ) texture_ - > Width ( ) ;
float texh = ( float ) texture_ - > Height ( ) ;
w = texw ;
h = texh ;
} else {
w = 16 ;
h = 16 ;
}
2015-07-02 10:37:13 +00:00
}
break ;
}
}
2023-12-14 11:23:31 +00:00
void HttpImageFileView : : SetFilename ( const std : : string & filename ) {
2023-06-18 10:13:40 +00:00
if ( ! useIconCache_ & & path_ ! = filename ) {
2015-07-02 10:37:13 +00:00
textureFailed_ = false ;
path_ = filename ;
2023-12-12 21:44:03 +00:00
if ( texture_ ) {
texture_ . reset ( nullptr ) ;
}
2015-07-02 10:37:13 +00:00
}
}
2023-07-21 20:04:05 +00:00
void HttpImageFileView : : DownloadCompletedCallback ( http : : Request & download ) {
2015-10-06 17:07:09 +00:00
if ( download . IsCancelled ( ) ) {
// We were probably destroyed. Can't touch "this" (heh).
return ;
}
2015-07-02 10:37:13 +00:00
if ( download . ResultCode ( ) = = 200 ) {
download . buffer ( ) . TakeAll ( & textureData_ ) ;
} else {
textureFailed_ = true ;
}
}
void HttpImageFileView : : Draw ( UIContext & dc ) {
2016-12-25 17:18:19 +00:00
using namespace Draw ;
2015-07-02 10:37:13 +00:00
2023-06-18 10:13:40 +00:00
if ( ! useIconCache_ ) {
if ( ! texture_ & & ! textureFailed_ & & ! path_ . empty ( ) & & ! download_ ) {
auto cb = std : : bind ( & HttpImageFileView : : DownloadCompletedCallback , this , std : : placeholders : : _1 ) ;
const char * acceptMime = " image/png, image/jpeg, image/*; q=0.9, */*; q=0.8 " ;
2023-07-20 09:25:27 +00:00
requestManager_ - > StartDownloadWithCallback ( path_ , Path ( ) , http : : ProgressBarMode : : NONE , cb , acceptMime ) ;
2023-06-18 10:13:40 +00:00
}
if ( ! textureData_ . empty ( ) ) {
2024-04-02 16:03:12 +00:00
texture_ = CreateTextureFromFileData ( dc . GetDrawContext ( ) , ( const uint8_t * ) ( textureData_ . data ( ) ) , textureData_ . size ( ) , ImageFileType : : DETECT , false , " store_icon " ) ;
2023-06-18 10:13:40 +00:00
if ( ! texture_ )
textureFailed_ = true ;
textureData_ . clear ( ) ;
download_ . reset ( ) ;
}
2015-07-02 10:37:13 +00:00
}
if ( HasFocus ( ) ) {
dc . FillRect ( dc . theme - > itemFocusedStyle . background , bounds_ . Expand ( 3 ) ) ;
}
// TODO: involve sizemode
2023-06-18 10:13:40 +00:00
Draw : : Texture * texture = nullptr ;
if ( useIconCache_ ) {
texture = g_iconCache . BindIconTexture ( & dc , path_ ) ;
} else {
2023-12-12 21:44:03 +00:00
texture = texture_ ;
2023-06-18 10:13:40 +00:00
}
if ( texture ) {
float tw = texture - > Width ( ) ;
float th = texture - > Height ( ) ;
2017-12-10 17:30:31 +00:00
float x = bounds_ . x ;
float y = bounds_ . y ;
float w = bounds_ . w ;
float h = bounds_ . h ;
if ( tw / th < w / h ) {
float nw = h * tw / th ;
x + = ( w - nw ) / 2.0f ;
w = nw ;
} else {
float nh = w * th / tw ;
y + = ( h - nh ) / 2.0f ;
h = nh ;
}
2015-07-02 10:37:13 +00:00
dc . Flush ( ) ;
2023-06-18 10:13:40 +00:00
dc . GetDrawContext ( ) - > BindTexture ( 0 , texture ) ;
2017-12-10 17:30:31 +00:00
dc . Draw ( ) - > Rect ( x , y , w , h , color_ ) ;
2015-07-02 10:37:13 +00:00
dc . Flush ( ) ;
dc . RebindTexture ( ) ;
} else {
// draw a black rectangle to represent the missing image.
2017-12-10 17:30:31 +00:00
dc . FillRect ( UI : : Drawable ( 0x7F000000 ) , GetBounds ( ) ) ;
2015-07-02 10:37:13 +00:00
}
}
2013-11-20 13:42:48 +00:00
// This is the entry in a list. Does not have install buttons and so on.
2017-12-10 08:44:44 +00:00
class ProductItemView : public UI : : StickyChoice {
2013-11-20 13:42:48 +00:00
public :
ProductItemView ( const StoreEntry & entry , UI : : LayoutParams * layoutParams = 0 )
2017-12-10 08:44:44 +00:00
: UI : : StickyChoice ( entry . name , " " , layoutParams ) , entry_ ( entry ) { }
2013-11-20 13:42:48 +00:00
2017-03-15 05:01:18 +00:00
void GetContentDimensions ( const UIContext & dc , float & w , float & h ) const override {
2013-11-20 13:42:48 +00:00
w = 300 ;
h = 164 ;
}
StoreEntry GetEntry ( ) const { return entry_ ; }
private :
2022-12-11 10:35:31 +00:00
const StoreEntry entry_ ;
2013-11-20 13:42:48 +00:00
} ;
2015-07-04 15:01:32 +00:00
// This is a "details" view of a game. Lets you install it.
2013-11-20 13:42:48 +00:00
class ProductView : public UI : : LinearLayout {
public :
2015-07-04 15:40:40 +00:00
ProductView ( const StoreEntry & entry )
2017-03-06 14:43:38 +00:00
: LinearLayout ( UI : : ORIENT_VERTICAL ) , entry_ ( entry ) {
2013-11-20 13:42:48 +00:00
CreateViews ( ) ;
}
2017-03-15 05:01:18 +00:00
void Update ( ) override ;
2013-11-20 13:42:48 +00:00
2015-07-04 16:05:17 +00:00
UI : : Event OnClickLaunch ;
2013-11-20 13:42:48 +00:00
private :
void CreateViews ( ) ;
UI : : EventReturn OnInstall ( UI : : EventParams & e ) ;
2017-03-06 14:43:38 +00:00
UI : : EventReturn OnCancel ( UI : : EventParams & e ) ;
2015-07-04 16:05:17 +00:00
UI : : EventReturn OnLaunchClick ( UI : : EventParams & e ) ;
2013-11-20 13:42:48 +00:00
2024-10-10 09:55:07 +00:00
bool IsGameInstalled ( ) const {
2015-07-04 15:40:40 +00:00
return g_GameManager . IsGameInstalled ( entry_ . file ) ;
}
2024-10-10 09:55:07 +00:00
std : : string DownloadURL ( ) const ;
2015-07-04 15:40:40 +00:00
2013-11-20 13:42:48 +00:00
StoreEntry entry_ ;
2023-12-08 13:05:12 +00:00
UI : : Button * uninstallButton_ = nullptr ;
2017-03-06 14:43:38 +00:00
UI : : Button * installButton_ = nullptr ;
2017-05-18 12:21:13 +00:00
UI : : Button * launchButton_ = nullptr ;
2017-03-06 14:43:38 +00:00
UI : : Button * cancelButton_ = nullptr ;
bool wasInstalled_ = false ;
2013-11-20 13:42:48 +00:00
} ;
void ProductView : : CreateViews ( ) {
using namespace UI ;
Clear ( ) ;
2015-07-02 10:37:13 +00:00
if ( ! entry_ . iconURL . empty ( ) ) {
2023-07-21 08:19:16 +00:00
Add ( new HttpImageFileView ( & g_DownloadManager , ResolveUrl ( StoreBaseUrl ( ) , entry_ . iconURL ) , IS_FIXED ) ) - > SetFixedSize ( 144 , 88 ) ;
2015-07-02 10:37:13 +00:00
}
2024-09-02 15:09:41 +00:00
Add ( new TextView ( entry_ . name ) ) - > SetBig ( true ) ;
2013-11-20 13:42:48 +00:00
Add ( new TextView ( entry_ . author ) ) ;
2023-04-05 22:34:50 +00:00
auto st = GetI18NCategory ( I18NCat : : STORE ) ;
auto di = GetI18NCategory ( I18NCat : : DIALOG ) ;
2015-07-04 15:40:40 +00:00
wasInstalled_ = IsGameInstalled ( ) ;
2021-05-01 17:49:44 +00:00
bool isDownloading = g_GameManager . IsDownloading ( DownloadURL ( ) ) ;
2015-07-04 15:40:40 +00:00
if ( ! wasInstalled_ ) {
2020-05-17 12:44:11 +00:00
launchButton_ = nullptr ;
2021-05-01 17:40:34 +00:00
LinearLayout * progressDisplay = new LinearLayout ( ORIENT_HORIZONTAL ) ;
installButton_ = progressDisplay - > Add ( new Button ( st - > T ( " Install " ) ) ) ;
2013-11-20 18:51:54 +00:00
installButton_ - > OnClick . Handle ( this , & ProductView : : OnInstall ) ;
2023-12-08 13:05:12 +00:00
uninstallButton_ = nullptr ;
2021-05-01 17:40:34 +00:00
Add ( progressDisplay ) ;
2013-11-20 13:42:48 +00:00
} else {
2015-07-04 15:40:40 +00:00
installButton_ = nullptr ;
2024-01-22 09:27:44 +00:00
launchButton_ = new Button ( st - > T ( " Launch Game " ) ) ;
launchButton_ - > OnClick . Handle ( this , & ProductView : : OnLaunchClick ) ;
Add ( launchButton_ ) ;
2023-12-08 13:05:12 +00:00
uninstallButton_ = new Button ( st - > T ( " Uninstall " ) ) ;
Add ( uninstallButton_ ) - > OnClick . Add ( [ = ] ( UI : : EventParams & e ) {
2023-12-08 12:10:26 +00:00
g_GameManager . UninstallGameOnThread ( entry_ . file ) ;
return UI : : EVENT_DONE ;
} ) ;
2024-09-02 15:09:41 +00:00
// Add(new TextView(st->T("Installed"))); // Not really needed
2013-11-20 13:42:48 +00:00
}
2013-11-20 15:36:58 +00:00
2017-03-06 14:43:38 +00:00
cancelButton_ = Add ( new Button ( di - > T ( " Cancel " ) ) ) ;
cancelButton_ - > OnClick . Handle ( this , & ProductView : : OnCancel ) ;
2021-05-01 17:49:44 +00:00
cancelButton_ - > SetVisibility ( isDownloading ? V_VISIBLE : V_GONE ) ;
2017-03-06 14:43:38 +00:00
2013-11-20 13:42:48 +00:00
// Add star rating, comments etc?
2021-08-14 03:45:31 +00:00
// Draw each line separately so focusing can scroll.
std : : vector < std : : string > lines ;
SplitString ( entry_ . description , ' \n ' , lines ) ;
for ( auto & line : lines ) {
Add ( new TextView ( line , ALIGN_LEFT | FLAG_WRAP_TEXT , false ) ) - > SetFocusable ( true ) ;
}
2013-11-20 13:42:48 +00:00
2021-08-14 03:45:31 +00:00
float size = entry_ . size / ( 1024.f * 1024.f ) ;
2024-01-19 12:44:49 +00:00
Add ( new TextView ( StringFromFormat ( " %s: %.2f %s " , st - > T_cstr ( " Size " ) , size , st - > T_cstr ( " MB " ) ) ) ) ;
2024-09-01 23:05:55 +00:00
if ( ! entry_ . license . empty ( ) ) {
LinearLayout * horiz = Add ( new LinearLayout ( ORIENT_HORIZONTAL ) ) ;
2024-09-02 15:09:41 +00:00
horiz - > Add ( new TextView ( StringFromFormat ( " %s: %s " , st - > T_cstr ( " License " ) , entry_ . license . c_str ( ) ) , new LinearLayoutParams ( 0.0 , G_VCENTER ) ) ) ;
horiz - > Add ( new Button ( di - > T ( " More information... " ) , new LinearLayoutParams ( 0.0 , G_VCENTER ) ) ) - > OnClick . Add ( [ this ] ( UI : : EventParams ) {
2024-09-01 23:05:55 +00:00
std : : string url = StringFromFormat ( " https://www.ppsspp.org/docs/reference/homebrew-store-distribution/#%s " , entry_ . file . c_str ( ) ) ;
System_LaunchUrl ( LaunchUrlType : : BROWSER_URL , url . c_str ( ) ) ;
return UI : : EVENT_DONE ;
} ) ;
}
if ( ! entry_ . websiteURL . empty ( ) ) {
// Display in a few different ways depending on the URL
size_t slashes = std : : count ( entry_ . websiteURL . begin ( ) , entry_ . websiteURL . end ( ) , ' / ' ) ;
std : : string buttonText ;
if ( slashes = = 2 ) {
// Just strip https and show the URL.
std : : string_view name = StripPrefix ( " https:// " , entry_ . websiteURL ) ;
name = StripPrefix ( " http:// " , name ) ;
if ( name . size ( ) < entry_ . websiteURL . size ( ) ) {
buttonText = name ;
}
}
if ( buttonText . empty ( ) ) {
// Fall back
buttonText = st - > T ( " Website " ) ;
}
Add ( new Button ( buttonText ) ) - > OnClick . Add ( [ this ] ( UI : : EventParams ) {
System_LaunchUrl ( LaunchUrlType : : BROWSER_URL , entry_ . websiteURL . c_str ( ) ) ;
return UI : : EVENT_DONE ;
} ) ;
}
2013-11-20 13:42:48 +00:00
}
2017-03-15 05:01:18 +00:00
void ProductView : : Update ( ) {
2015-07-04 15:40:40 +00:00
if ( wasInstalled_ ! = IsGameInstalled ( ) ) {
CreateViews ( ) ;
}
if ( installButton_ ) {
2017-03-06 14:43:38 +00:00
installButton_ - > SetEnabled ( g_GameManager . GetState ( ) = = GameManagerState : : IDLE ) ;
2015-07-04 15:40:40 +00:00
}
2023-12-08 13:05:12 +00:00
if ( uninstallButton_ ) {
uninstallButton_ - > SetEnabled ( g_GameManager . GetState ( ) = = GameManagerState : : IDLE ) ;
}
2024-01-30 13:50:55 +00:00
if ( g_GameManager . GetState ( ) ! = GameManagerState : : DOWNLOADING ) {
2021-05-01 17:40:34 +00:00
if ( cancelButton_ )
cancelButton_ - > SetVisibility ( UI : : V_GONE ) ;
}
2017-05-18 12:21:13 +00:00
if ( launchButton_ )
launchButton_ - > SetEnabled ( g_GameManager . GetState ( ) = = GameManagerState : : IDLE ) ;
2024-10-10 09:55:07 +00:00
ViewGroup : : Update ( ) ;
2013-11-20 13:42:48 +00:00
}
2024-10-10 09:55:07 +00:00
std : : string ProductView : : DownloadURL ( ) const {
2013-11-29 16:34:21 +00:00
if ( entry_ . downloadURL . empty ( ) ) {
2021-07-12 09:09:39 +00:00
// Construct the URL.
2023-07-21 08:19:16 +00:00
return StoreBaseUrl ( ) + " files/ " + entry_ . file + " .zip " ;
2013-11-29 16:34:21 +00:00
} else {
// Use the provided URL, for external hosting.
2021-05-01 17:49:44 +00:00
return entry_ . downloadURL ;
2013-11-29 16:34:21 +00:00
}
2021-05-01 17:49:44 +00:00
}
UI : : EventReturn ProductView : : OnInstall ( UI : : EventParams & e ) {
std : : string fileUrl = DownloadURL ( ) ;
2013-11-20 13:42:48 +00:00
if ( installButton_ ) {
2013-11-20 18:51:54 +00:00
installButton_ - > SetEnabled ( false ) ;
2013-11-20 13:42:48 +00:00
}
2017-03-06 14:43:38 +00:00
if ( cancelButton_ ) {
cancelButton_ - > SetVisibility ( UI : : V_VISIBLE ) ;
}
2024-07-14 12:42:59 +00:00
INFO_LOG ( Log : : System , " Triggering install of '%s' " , fileUrl . c_str ( ) ) ;
2019-07-10 21:37:10 +00:00
g_GameManager . DownloadAndInstall ( fileUrl ) ;
2013-11-20 13:42:48 +00:00
return UI : : EVENT_DONE ;
}
2017-03-06 14:43:38 +00:00
UI : : EventReturn ProductView : : OnCancel ( UI : : EventParams & e ) {
g_GameManager . CancelDownload ( ) ;
return UI : : EVENT_DONE ;
}
2015-07-04 16:05:17 +00:00
UI : : EventReturn ProductView : : OnLaunchClick ( UI : : EventParams & e ) {
2017-05-18 12:21:13 +00:00
if ( g_GameManager . GetState ( ) ! = GameManagerState : : IDLE ) {
// Button should have been disabled. Just a safety check.
return UI : : EVENT_DONE ;
}
2021-05-05 23:31:38 +00:00
Path pspGame = GetSysDirectory ( DIRECTORY_GAME ) ;
Path path = pspGame / entry_ . file ;
2017-03-22 01:34:52 +00:00
UI : : EventParams e2 { } ;
e2 . v = e . v ;
2021-05-05 23:31:38 +00:00
e2 . s = path . ToString ( ) ;
2015-07-04 16:05:17 +00:00
// Insta-update - here we know we are already on the right thread.
OnClickLaunch . Trigger ( e2 ) ;
return UI : : EVENT_DONE ;
}
2013-11-20 13:42:48 +00:00
2017-12-10 08:44:44 +00:00
StoreScreen : : StoreScreen ( ) {
2013-11-20 13:42:48 +00:00
lang_ = g_Config . sLanguageIni ;
loading_ = true ;
2023-07-21 08:19:16 +00:00
std : : string indexPath = StoreBaseUrl ( ) + " index.json " ;
2021-08-22 15:29:48 +00:00
const char * acceptMime = " application/json, */*; q=0.8 " ;
2023-07-18 13:13:44 +00:00
listing_ = g_DownloadManager . StartDownload ( indexPath , Path ( ) , http : : ProgressBarMode : : DELAYED , acceptMime ) ;
2013-11-20 13:42:48 +00:00
}
StoreScreen : : ~ StoreScreen ( ) {
2013-11-29 15:33:17 +00:00
g_DownloadManager . CancelAll ( ) ;
2013-11-20 13:42:48 +00:00
}
// Handle async download tasks
2017-03-15 05:01:18 +00:00
void StoreScreen : : update ( ) {
UIDialogScreenWithBackground : : update ( ) ;
2013-11-20 13:42:48 +00:00
2013-11-29 15:33:17 +00:00
g_DownloadManager . Update ( ) ;
2013-11-20 13:42:48 +00:00
2024-10-10 09:55:07 +00:00
if ( listing_ . get ( ) & & listing_ - > Done ( ) ) {
2021-08-22 10:21:44 +00:00
resultCode_ = listing_ - > ResultCode ( ) ;
2013-11-20 13:42:48 +00:00
if ( listing_ - > ResultCode ( ) = = 200 ) {
std : : string listingJson ;
listing_ - > buffer ( ) . TakeAll ( & listingJson ) ;
2013-12-05 11:42:42 +00:00
// printf("%s\n", listingJson.c_str());
2013-11-20 13:42:48 +00:00
loading_ = false ;
2015-07-19 15:33:38 +00:00
connectionError_ = false ;
2013-11-20 13:42:48 +00:00
ParseListing ( listingJson ) ;
RecreateViews ( ) ;
} else {
// Failed to contact store. Don't do anything.
2024-07-14 12:42:59 +00:00
ERROR_LOG ( Log : : IO , " Download failed : error code %d " , resultCode_ ) ;
2013-11-20 13:42:48 +00:00
connectionError_ = true ;
2015-07-19 15:33:38 +00:00
loading_ = false ;
2013-11-20 13:42:48 +00:00
RecreateViews ( ) ;
}
// Forget the listing.
listing_ . reset ( ) ;
}
}
2023-12-14 11:23:31 +00:00
void StoreScreen : : ParseListing ( const std : : string & json ) {
2018-08-12 22:05:00 +00:00
using namespace json ;
2013-11-20 13:42:48 +00:00
JsonReader reader ( json . c_str ( ) , json . size ( ) ) ;
2018-04-15 18:24:10 +00:00
if ( ! reader . ok ( ) | | ! reader . root ( ) ) {
2024-07-14 12:42:59 +00:00
ERROR_LOG ( Log : : IO , " Error parsing JSON from store " ) ;
2013-11-20 13:42:48 +00:00
connectionError_ = true ;
RecreateViews ( ) ;
return ;
}
2018-04-15 18:24:10 +00:00
const JsonGet root = reader . root ( ) ;
const JsonNode * entries = root . getArray ( " entries " ) ;
2013-11-20 13:42:48 +00:00
if ( entries ) {
entries_ . clear ( ) ;
2018-04-15 18:24:10 +00:00
for ( const JsonNode * pgame : entries - > value ) {
JsonGet game = pgame - > value ;
2024-05-06 11:50:56 +00:00
StoreEntry e { } ;
2013-11-20 13:42:48 +00:00
e . type = ENTRY_PBPZIP ;
e . name = GetTranslatedString ( game , " name " ) ;
e . description = GetTranslatedString ( game , " description " , " " ) ;
2024-09-25 20:25:08 +00:00
e . author = ReplaceAll ( game . getStringOr ( " author " , " ? " ) , " && " , " & " ) ; // Can't remove && in the JSON source data due to old app versions, so we do the opposite replacement here.
2018-04-15 18:24:10 +00:00
e . size = game . getInt ( " size " ) ;
2024-01-16 12:14:57 +00:00
e . downloadURL = game . getStringOr ( " download-url " , " " ) ;
e . iconURL = game . getStringOr ( " icon-url " , " " ) ;
2024-05-06 11:50:56 +00:00
e . contentRating = game . getInt ( " content-rating " , 0 ) ;
2024-09-01 23:05:55 +00:00
e . websiteURL = game . getStringOr ( " website-url " , " " ) ;
e . license = game . getStringOr ( " license " , " " ) ;
2024-05-06 11:50:56 +00:00
# if PPSSPP_PLATFORM(IOS_APP_STORE)
if ( e . contentRating > = 100 ) {
continue ;
}
# endif
2022-12-11 10:35:31 +00:00
e . hidden = false ; // NOTE: Handling of the "hidden" flag is broken in old versions of PPSSPP. Do not use.
2024-01-16 12:14:57 +00:00
const char * file = game . getStringOr ( " file " , nullptr ) ;
2013-11-20 13:42:48 +00:00
if ( ! file )
continue ;
e . file = file ;
entries_ . push_back ( e ) ;
}
}
}
void StoreScreen : : CreateViews ( ) {
using namespace UI ;
2013-11-29 12:02:08 +00:00
root_ = new LinearLayout ( ORIENT_VERTICAL ) ;
2013-12-14 13:50:18 +00:00
2023-04-05 22:34:50 +00:00
auto di = GetI18NCategory ( I18NCat : : DIALOG ) ;
auto mm = GetI18NCategory ( I18NCat : : MAINMENU ) ;
2013-11-29 12:02:08 +00:00
// Top bar
2024-05-06 15:33:33 +00:00
LinearLayout * topBar = root_ - > Add ( new LinearLayout ( ORIENT_HORIZONTAL , new LinearLayoutParams ( FILL_PARENT , 64.0f ) ) ) ;
topBar - > Add ( new Choice ( di - > T ( " Back " ) , new LinearLayoutParams ( WRAP_CONTENT , FILL_PARENT ) ) ) - > OnClick . Handle < UIScreen > ( this , & UIScreen : : OnBack ) ;
titleText_ = new TextView ( mm - > T ( " PPSSPP Homebrew Store " ) , ALIGN_VCENTER , false , new LinearLayoutParams ( WRAP_CONTENT , FILL_PARENT ) ) ;
2017-03-06 14:43:38 +00:00
topBar - > Add ( titleText_ ) ;
2013-11-29 12:02:08 +00:00
UI : : Drawable solid ( 0xFFbd9939 ) ;
topBar - > SetBG ( solid ) ;
LinearLayout * content ;
2013-11-20 13:42:48 +00:00
if ( connectionError_ | | loading_ ) {
2023-12-20 09:35:02 +00:00
auto st = GetI18NCategory ( I18NCat : : STORE ) ;
2013-12-05 16:08:20 +00:00
content = new LinearLayout ( ORIENT_VERTICAL , new LinearLayoutParams ( FILL_PARENT , FILL_PARENT , 1.0f ) ) ;
2024-01-19 12:44:49 +00:00
content - > Add ( new TextView ( loading_ ? std : : string ( st - > T ( " Loading... " ) ) : StringFromFormat ( " %s: %d " , st - > T_cstr ( " Connection Error " ) , resultCode_ ) ) ) ;
2022-12-11 23:09:04 +00:00
if ( ! loading_ ) {
content - > Add ( new Button ( di - > T ( " Retry " ) ) ) - > OnClick . Handle ( this , & StoreScreen : : OnRetry ) ;
}
2013-12-14 13:50:18 +00:00
content - > Add ( new Button ( di - > T ( " Back " ) ) ) - > OnClick . Handle < UIScreen > ( this , & UIScreen : : OnBack ) ;
2017-12-10 08:44:44 +00:00
scrollItemView_ = nullptr ;
productPanel_ = nullptr ;
2013-11-20 13:42:48 +00:00
} else {
2013-12-05 16:08:20 +00:00
content = new LinearLayout ( ORIENT_HORIZONTAL , new LinearLayoutParams ( FILL_PARENT , FILL_PARENT , 1.0f ) ) ;
ScrollView * leftScroll = new ScrollView ( ORIENT_VERTICAL , new LinearLayoutParams ( WRAP_CONTENT , FILL_PARENT , 0.4f ) ) ;
2016-01-23 06:52:13 +00:00
leftScroll - > SetTag ( " StoreMainList " ) ;
2013-11-29 12:02:08 +00:00
content - > Add ( leftScroll ) ;
2021-02-22 02:41:08 +00:00
scrollItemView_ = new LinearLayoutList ( ORIENT_VERTICAL , new LayoutParams ( FILL_PARENT , WRAP_CONTENT ) ) ;
2017-12-10 08:44:44 +00:00
leftScroll - > Add ( scrollItemView_ ) ;
2013-12-05 16:08:20 +00:00
2022-12-11 10:35:31 +00:00
for ( size_t i = 0 ; i < entries_ . size ( ) ; i + + ) {
2017-12-10 08:44:44 +00:00
scrollItemView_ - > Add ( new ProductItemView ( entries_ [ i ] ) ) - > OnClick . Handle ( this , & StoreScreen : : OnGameSelected ) ;
2013-11-20 13:42:48 +00:00
}
// TODO: Similar apps, etc etc
productPanel_ = new ScrollView ( ORIENT_VERTICAL , new LinearLayoutParams ( 0.5f ) ) ;
2016-01-23 06:52:13 +00:00
leftScroll - > SetTag ( " StoreMainProduct " ) ;
2013-11-29 12:02:08 +00:00
content - > Add ( productPanel_ ) ;
2017-12-10 08:44:44 +00:00
ProductItemView * selectedItem = GetSelectedItem ( ) ;
if ( selectedItem ) {
ProductView * productView = new ProductView ( selectedItem - > GetEntry ( ) ) ;
productView - > OnClickLaunch . Handle ( this , & StoreScreen : : OnGameLaunch ) ;
productPanel_ - > Add ( productView ) ;
selectedItem - > Press ( ) ;
} else {
2022-09-30 09:26:30 +00:00
lastSelectedName_ . clear ( ) ;
2017-12-10 08:44:44 +00:00
}
2013-11-20 13:42:48 +00:00
}
2013-11-29 12:02:08 +00:00
root_ - > Add ( content ) ;
2013-11-20 13:42:48 +00:00
}
2017-12-10 08:44:44 +00:00
ProductItemView * StoreScreen : : GetSelectedItem ( ) {
for ( int i = 0 ; i < scrollItemView_ - > GetNumSubviews ( ) ; + + i ) {
2024-10-10 09:55:07 +00:00
ProductItemView * item = dynamic_cast < ProductItemView * > ( scrollItemView_ - > GetViewByIndex ( i ) ) ;
if ( ! item ) {
continue ;
}
2017-12-10 08:44:44 +00:00
if ( item - > GetEntry ( ) . name = = lastSelectedName_ )
return item ;
}
return nullptr ;
}
2013-11-20 13:42:48 +00:00
UI : : EventReturn StoreScreen : : OnGameSelected ( UI : : EventParams & e ) {
2024-10-10 09:55:07 +00:00
ProductItemView * item = dynamic_cast < ProductItemView * > ( e . v ) ;
2013-11-20 13:42:48 +00:00
if ( ! item )
return UI : : EVENT_DONE ;
productPanel_ - > Clear ( ) ;
2015-07-04 16:05:17 +00:00
ProductView * productView = new ProductView ( item - > GetEntry ( ) ) ;
productView - > OnClickLaunch . Handle ( this , & StoreScreen : : OnGameLaunch ) ;
productPanel_ - > Add ( productView ) ;
2017-12-10 08:44:44 +00:00
ProductItemView * previousItem = GetSelectedItem ( ) ;
if ( previousItem & & previousItem ! = item )
previousItem - > Release ( ) ;
lastSelectedName_ = item - > GetEntry ( ) . name ;
2015-07-04 16:05:17 +00:00
return UI : : EVENT_DONE ;
}
UI : : EventReturn StoreScreen : : OnGameLaunch ( UI : : EventParams & e ) {
std : : string path = e . s ;
2021-05-05 23:31:38 +00:00
screenManager ( ) - > switchScreen ( new EmuScreen ( Path ( path ) ) ) ;
2013-11-20 13:42:48 +00:00
return UI : : EVENT_DONE ;
}
UI : : EventReturn StoreScreen : : OnRetry ( UI : : EventParams & e ) {
2022-12-11 19:42:18 +00:00
RecreateViews ( ) ;
2013-11-20 13:42:48 +00:00
return UI : : EVENT_DONE ;
}
2023-12-14 11:23:31 +00:00
std : : string StoreScreen : : GetTranslatedString ( const json : : JsonGet json , const std : : string & key , const char * fallback ) const {
2018-08-12 22:05:00 +00:00
json : : JsonGet dict = json . getDict ( " en_US " ) ;
2018-04-15 18:24:10 +00:00
if ( dict & & json . hasChild ( lang_ . c_str ( ) , JSON_OBJECT ) ) {
if ( json . getDict ( lang_ . c_str ( ) ) . hasChild ( key . c_str ( ) , JSON_STRING ) ) {
dict = json . getDict ( lang_ . c_str ( ) ) ;
2013-11-20 13:42:48 +00:00
}
}
2018-04-15 18:24:10 +00:00
const char * str = nullptr ;
2013-11-20 13:42:48 +00:00
if ( dict ) {
2024-01-16 12:14:57 +00:00
str = dict . getStringOr ( key . c_str ( ) , nullptr ) ;
2013-11-20 13:42:48 +00:00
}
if ( str ) {
return std : : string ( str ) ;
} else {
return fallback ? fallback : " (error) " ;
}
}