美文网首页Flutter&Dart
哥哥带你一步步搭建Flutter框架

哥哥带你一步步搭建Flutter框架

作者: 哥哥是欧巴Vitory | 来源:发表于2019-08-21 16:57 被阅读8次

    先来整体了解一下Flutter整体框架结构:

    1,Flutter网络框架加持:

    详细哥哥已经在之前的两篇文章中做过介绍,有兴趣的请移步:

    https://www.jianshu.com/p/90d693395347

    https://www.jianshu.com/p/f0806375668d

    2,跳转工具加持:

    class NavigatorUtil {

    static void pushPage(

    BuildContext context,

        Widget page, {

    String pageName,

        bool needLogin =false,

      }) {

    if (context ==null || page ==null)return;

        if (needLogin && !Util.isLogin()) {

    pushPage(context, UserLoginPage());

    return;

        }

    Navigator.push(

    context, new CupertinoPageRoute(builder: (ctx) => page));

      }

    static void pushWeb(BuildContext context,

          {String title, String titleId, String url, bool isHome:false}) {

    if (context ==null || ObjectUtil.isEmpty(url))return;

        if (url.endsWith(".apk")) {

    launchInBrowser(url, title: title ?? titleId);

        }else {

    Navigator.push(

    context,

              new CupertinoPageRoute(

    builder: (ctx) =>new WebScaffold(

    title: title,

                        titleId: titleId,

                        url: url,

                      )));

        }

    }

    static void pushTabPage(BuildContext context,

          {String labelId, String title, String titleId, TreeModel treeModel}) {

    if (context ==null)return;

        Navigator.push(

    context,

            new CupertinoPageRoute(

    builder: (ctx) =>new BlocProvider(

    child:new TabPage(

    labelId: labelId,

                        title: title,

                        titleId: titleId,

                        treeModel: treeModel,

                      ),

                      bloc:new TabBloc(),

                    )));

      }

    static FuturelaunchInBrowser(String url, {String title})async {

    if (await canLaunch(url)) {

    await launch(url, forceSafariVC:false, forceWebView:false);

        }else {

    throw 'Could not launch $url';

        }

    }

    }

    3,数据类加持

    开发思路:把所有接口返回数据都写入一个dart文件中,方便调用。

    import 'package:flutter/widgets.dart';

    class LanguageModel {

    StringtitleId;

      StringlanguageCode;

      StringcountryCode;

      boolisSelected;

      LanguageModel(this.titleId, this.languageCode, this.countryCode,

          {this.isSelected:false});

      LanguageModel.fromJson(Map json)

    :titleId = json['titleId'],

            languageCode = json['languageCode'],

            countryCode = json['countryCode'],

            isSelected = json['isSelected'];

      MaptoJson() => {

    'titleId':titleId,

            'languageCode':languageCode,

            'countryCode':countryCode,

            'isSelected':isSelected,

          };

      @override

      StringtoString() {

    StringBuffer sb =new StringBuffer('{');

        sb.write("\"titleId\":\"$titleId\"");

        sb.write(",\"languageCode\":\"$languageCode\"");

        sb.write(",\"countryCode\":\"$countryCode\"");

        sb.write('}');

        return sb.toString();

      }

    }

    class SplashModel {

    Stringtitle;

      Stringcontent;

      Stringurl;

      StringimgUrl;

      SplashModel({this.title, this.content, this.url, this.imgUrl});

      SplashModel.fromJson(Map json)

    :title = json['title'],

            content = json['content'],

            url = json['url'],

            imgUrl = json['imgUrl'];

      MaptoJson() => {

    'title':title,

            'content':content,

            'url':url,

            'imgUrl':imgUrl,

          };

      @override

      StringtoString() {

    StringBuffer sb =new StringBuffer('{');

        sb.write("\"title\":\"$title\"");

        sb.write(",\"content\":\"$content\"");

        sb.write(",\"url\":\"$url\"");

        sb.write(",\"imgUrl\":\"$imgUrl\"");

        sb.write('}');

        return sb.toString();

      }

    }

    class VersionModel {

    Stringtitle;

      Stringcontent;

      Stringurl;

      Stringversion;

      VersionModel({this.title, this.content, this.url, this.version});

      VersionModel.fromJson(Map json)

    :title = json['title'],

            content = json['content'],

            url = json['url'],

            version = json['version'];

      MaptoJson() => {

    'title':title,

            'content':content,

            'url':url,

            'version':version,

          };

      @override

      StringtoString() {

    StringBuffer sb =new StringBuffer('{');

        sb.write("\"title\":\"$title\"");

        sb.write(",\"content\":\"$content\"");

        sb.write(",\"url\":\"$url\"");

        sb.write(",\"version\":\"$version\"");

        sb.write('}');

        return sb.toString();

      }

    }

    class ComModel {

    Stringversion;

      Stringtitle;

      Stringcontent;

      Stringextra;

      Stringurl;

      StringimgUrl;

      Stringauthor;

      StringupdatedAt;

      inttypeId;

      StringtitleId;

      Widgetpage;

      ComModel(

    {this.version,

          this.title,

          this.content,

          this.extra,

          this.url,

          this.imgUrl,

          this.author,

          this.updatedAt,

          this.typeId,

          this.titleId,

          this.page});

      ComModel.fromJson(Map json)

    :version = json['version'],

            title = json['title'],

            content = json['content'],

            extra = json['extra'],

            url = json['url'],

            imgUrl = json['imgUrl'],

            author = json['author'],

            updatedAt = json['updatedAt'];

      MaptoJson() => {

    'version':version,

            'title':title,

            'content':content,

            'extra':extra,

            'url':url,

            'imgUrl':imgUrl,

            'author':author,

            'updatedAt':updatedAt,

          };

      @override

      StringtoString() {

    StringBuffer sb =new StringBuffer('{');

        sb.write("\"version\":\"$version\"");

        sb.write(",\"title\":\"$title\"");

        sb.write(",\"content\":\"$content\"");

        sb.write(",\"url\":\"$url\"");

        sb.write(",\"imgUrl\":\"$imgUrl\"");

        sb.write(",\"author\":\"$author\"");

        sb.write(",\"updatedAt\":\"$updatedAt\"");

        sb.write('}');

        return sb.toString();

      }

    }

    4,res资源文件加持

    不同于Android,有专门的res资源文件管理,那我们就仿造Android来创建一个res包,来实现对各种不同资源的管理。

    1,colors

    import 'package:flutter/material.dart';

    class _Colours {

    static const Colorapp_main =Color(0xFF666666);

      static const Colortransparent_80 =Color(0x80000000); //

      static const Colortext_dark =Color(0xFF333333);

      static const Colortext_normal =Color(0xFF666666);

      static const Colortext_gray =Color(0xFF999999);

      static const Colordivider =Color(0xffe5e5e5);

      static const Colorgray_33 =Color(0xFF333333); //51

      static const Colorgray_66 =Color(0xFF666666); //102

      static const Colorgray_99 =Color(0xFF999999); //153

      static const Colorcommon_orange =Color(0XFFFC9153); //252 145 83

      static const Colorgray_ef =Color(0XFFEFEFEF); //153

      static const Colorgray_f0 =Color(0xfff0f0f0); //

      static const Colorgray_f5 =Color(0xfff5f5f5); //

      static const Colorgray_cc =Color(0xffcccccc); //

      static const Colorgray_ce =Color(0xffcecece); //

      static const Colorgreen_1 =Color(0xff009688); //

      static const Colorgreen_62 =Color(0xff626262); //

      static const Colorgreen_e5 =Color(0xffe5e5e5); //

    }

    Map circleAvatarMap = {

    'A': Colors.blueAccent,

      'B': Colors.blue,

      'C': Colors.cyan,

      'D': Colors.deepPurple,

      'E': Colors.deepPurpleAccent,

      'F': Colors.blue,

      'G': Colors.green,

      'H': Colors.lightBlue,

      'I': Colors.indigo,

      'J': Colors.blue,

      'K': Colors.blue,

      'L': Colors.lightGreen,

      'M': Colors.blue,

      'N': Colors.brown,

      'O': Colors.orange,

      'P': Colors.purple,

      'Q': Colors.black,

      'R': Colors.red,

      'S': Colors.blue,

      'T': Colors.teal,

      'U': Colors.purpleAccent,

      'V': Colors.black,

      'W': Colors.brown,

      'X': Colors.blue,

      'Y': Colors.yellow,

      'Z': Colors.grey,

      '#': Colors.blue,

    };

    Map themeColorMap = {

    'gray': _Colours.gray_33,

      'blue': Colors.blue,

      'blueAccent': Colors.blueAccent,

      'cyan': Colors.cyan,

      'deepPurple': Colors.deepPurple,

      'deepPurpleAccent': Colors.deepPurpleAccent,

      'deepOrange': Colors.deepOrange,

      'green': Colors.green,

      'indigo': Colors.indigo,

      'indigoAccent': Colors.indigoAccent,

      'orange': Colors.orange,

      'purple': Colors.purple,

      'pink': Colors.pink,

      'red': Colors.red,

      'teal': Colors.teal,

      'black': Colors.black,

    };

    2,dimens

    class Dimens {

    static const doublefont_sp10 =10;

      static const doublefont_sp12 =12;

      static const doublefont_sp14 =14;

      static const doublefont_sp16 =16;

      static const doublefont_sp18 =18;

      static const doublegap_dp5 =5;

      static const doublegap_dp10 =10;

      static const doublegap_dp12 =12;

      static const doublegap_dp15 =15;

      static const doublegap_dp16 =16;

    }

    3,strings
    class Ids {

    static const StringtitleHome ='title_home';

      static const StringtitleRepos ='title_repos';

      static const StringtitleEvents ='title_events';

      static const StringtitleSystem ='title_system';

      static const StringtitleBookmarks ='title_bookmarks';

      static const StringtitleCollection ='title_collection';

      static const StringtitleSetting ='title_setting';

      static const StringtitleAbout ='title_about';

      static const StringtitleShare ='title_share';

      static const StringtitleSignOut ='title_signout';

      static const StringtitleLanguage ='title_language';

      static const StringtitleTheme ='title_theme';

      static const StringtitleAuthor ='title_author';

      static const StringtitleOther ='title_other';

      static const StringlanguageAuto ='language_auto';

      static const StringlanguageZH ='language_zh';

      static const StringlanguageTW ='language_tw';

      static const StringlanguageHK ='language_hk';

      static const StringlanguageEN ='language_en';

      static const Stringsave ='save';

      static const Stringmore ='more';

      static const StringrecRepos ='rec_repos';

      static const StringrecWxArticle ='rec_wxarticle';

      static const StringtitleReposTree ='title_repos_tree';

      static const StringtitleWxArticleTree ='title_wxarticle_tree';

      static const StringtitleSystemTree ='title_system_tree';

      static const Stringuser_name ='user_name';

      static const Stringuser_pwd ='user_pwd';

      static const Stringuser_re_pwd ='user_re_pwd';

      static const Stringuser_login ='user_login';

      static const Stringuser_register ='user_register';

      static const Stringuser_forget_pwd ='user_forget_pwd';

      static const Stringuser_new_user_hint ='user_new_user_hint';

      static const Stringconfirm ='confirm';

      static const Stringcancel ='cancel';

      static const Stringjump_count ='jump_count';

    }

    Map> localizedSimpleValues = {

    'en': {

    Ids.titleHome:'Home',

        Ids.titleRepos:'Repos',

        Ids.titleEvents:'Events',

        Ids.titleSystem:'System',

        Ids.titleBookmarks:'Bookmarks',

        Ids.titleSetting:'Setting',

        Ids.titleAbout:'About',

        Ids.titleShare:'Share',

        Ids.titleSignOut:'Sign Out',

        Ids.titleLanguage:'Language',

        Ids.languageAuto:'Auto',

      },

      'zh': {

    Ids.titleHome:'主页',

        Ids.titleRepos:'项目',

        Ids.titleEvents:'动态',

        Ids.titleSystem:'体系',

        Ids.titleBookmarks:'书签',

        Ids.titleSetting:'设置',

        Ids.titleAbout:'关于',

        Ids.titleShare:'分享',

        Ids.titleSignOut:'注销',

        Ids.titleLanguage:'多语言',

        Ids.languageAuto:'跟随系统',

      },

    };

    Map>> localizedValues = {

    'en': {

    'US': {

    Ids.titleHome:'Home',

          Ids.titleRepos:'Repos',

          Ids.titleEvents:'Events',

          Ids.titleSystem:'System',

          Ids.titleBookmarks:'Bookmarks',

          Ids.titleCollection:'Collection',

          Ids.titleSetting:'Setting',

          Ids.titleAbout:'About',

          Ids.titleShare:'Share',

          Ids.titleSignOut:'Sign Out',

          Ids.titleLanguage:'Language',

          Ids.languageAuto:'Auto',

          Ids.save:'Save',

          Ids.more:'More',

          Ids.recRepos:'Reco Repos',

          Ids.recWxArticle:'Reco WxArticle',

          Ids.titleReposTree:'Repos Tree',

          Ids.titleWxArticleTree:'Wx Article',

          Ids.titleTheme:'Theme',

          Ids.user_name:'user name',

          Ids.user_pwd:'password',

          Ids.user_re_pwd:'confirm password',

          Ids.user_login:'Login',

          Ids.user_register:'Register',

          Ids.user_forget_pwd:'Forget the password?',

          Ids.user_new_user_hint:'New users? ',

          Ids.confirm:'Confirm',

          Ids.cancel:'Cancel',

          Ids.jump_count:'Jump %\$0\$s',

        }

    },

      'zh': {

    'CN': {

    Ids.titleHome:'主页',

          Ids.titleRepos:'项目',

          Ids.titleEvents:'动态',

          Ids.titleSystem:'体系',

          Ids.titleBookmarks:'书签',

          Ids.titleCollection:'收藏',

          Ids.titleSetting:'设置',

          Ids.titleAbout:'关于',

          Ids.titleShare:'分享',

          Ids.titleSignOut:'注销',

          Ids.titleLanguage:'多语言',

          Ids.languageAuto:'跟随系统',

          Ids.languageZH:'简体中文',

          Ids.languageTW:'繁體中文(台灣)',

          Ids.languageHK:'繁體中文(香港)',

          Ids.languageEN:'English',

          Ids.save:'保存',

          Ids.more:'更多',

          Ids.recRepos:'推荐项目',

          Ids.recWxArticle:'推荐公众号',

          Ids.titleReposTree:'项目分类',

          Ids.titleWxArticleTree:'公众号',

          Ids.titleTheme:'主题',

          Ids.user_name:'用户名',

          Ids.user_pwd:'密码',

          Ids.user_re_pwd:'确认密码',

          Ids.user_login:'登录',

          Ids.user_register:'注册',

          Ids.user_forget_pwd:'忘记密码?',

          Ids.user_new_user_hint:'新用户?',

          Ids.confirm:'确认',

          Ids.cancel:'取消',

          Ids.jump_count:'跳过 %\$0\$s',

        },

        'HK': {

    Ids.titleHome:'主頁',

          Ids.titleRepos:'項目',

          Ids.titleEvents:'動態',

          Ids.titleSystem:'體系',

          Ids.titleBookmarks:'書簽',

          Ids.titleCollection:'收藏',

          Ids.titleSetting:'設置',

          Ids.titleAbout:'關於',

          Ids.titleShare:'分享',

          Ids.titleSignOut:'註銷',

          Ids.titleLanguage:'語言',

          Ids.languageAuto:'與系統同步',

          Ids.save:'儲存',

          Ids.more:'更多',

          Ids.recRepos:'推荐项目',

          Ids.recWxArticle:'推荐公众号',

          Ids.titleReposTree:'项目分类',

          Ids.titleWxArticleTree:'公众号',

          Ids.titleTheme:'主題',

          Ids.user_name:'用户名',

          Ids.user_pwd:'密码',

          Ids.user_re_pwd:'确认密码',

          Ids.user_login:'登录',

          Ids.user_register:'注册',

          Ids.user_forget_pwd:'忘记密码?',

          Ids.user_new_user_hint:'新用户?',

          Ids.confirm:'确认',

          Ids.cancel:'取消',

          Ids.jump_count:'跳过 %\$0\$s',

        },

        'TW': {

    Ids.titleHome:'主頁',

          Ids.titleRepos:'項目',

          Ids.titleEvents:'動態',

          Ids.titleSystem:'體系',

          Ids.titleBookmarks:'書簽',

          Ids.titleCollection:'收藏',

          Ids.titleSetting:'設置',

          Ids.titleAbout:'關於',

          Ids.titleShare:'分享',

          Ids.titleSignOut:'註銷',

          Ids.titleLanguage:'語言',

          Ids.languageAuto:'與系統同步',

          Ids.save:'儲存',

          Ids.more:'更多',

          Ids.recRepos:'推荐项目',

          Ids.recWxArticle:'推荐公众号',

          Ids.titleReposTree:'项目分类',

          Ids.titleWxArticleTree:'公众号',

          Ids.titleTheme:'主題',

          Ids.user_name:'用户名',

          Ids.user_pwd:'密码',

          Ids.user_re_pwd:'确认密码',

          Ids.user_login:'登录',

          Ids.user_register:'注册',

          Ids.user_forget_pwd:'忘记密码?',

          Ids.user_new_user_hint:'新用户?',

          Ids.confirm:'确认',

          Ids.cancel:'取消',

          Ids.jump_count:'跳过 %\$0\$s',

        }

    }

    };

    4,styles
    class TextStyles {

    static TextStylelistTitle =TextStyle(

    fontSize: Dimens.font_sp16,

        color: Colours.text_dark,

        fontWeight: FontWeight.bold,

      );

      static TextStylelistContent =TextStyle(

    fontSize: Dimens.font_sp14,

        color: Colours.text_normal,

      );

      static TextStylelistExtra =TextStyle(

    fontSize: Dimens.font_sp12,

        color: Colours.text_gray,

      );

    }

    class Decorations {

    static Decorationbottom =BoxDecoration(

    border:Border(bottom:BorderSide(width:0.33, color: Colours.divider)));

    }

    /// 间隔

    class Gaps {

    /// 水平间隔

      static WidgethGap5 =new SizedBox(width: Dimens.gap_dp5);

      static WidgethGap10 =new SizedBox(width: Dimens.gap_dp10);

      static WidgethGap15 =new SizedBox(width: Dimens.gap_dp15);

      /// 垂直间隔

      static WidgetvGap5 =new SizedBox(height: Dimens.gap_dp5);

      static WidgetvGap10 =new SizedBox(height: Dimens.gap_dp10);

      static WidgetvGap15 =new SizedBox(height: Dimens.gap_dp15);

    }

    5,事件总线加持

    class StatusEvent {

    StringlabelId;

      intstatus;

      intcid;

      StatusEvent(this.labelId, this.status, {this.cid});

    }

    class ComEvent {

    intid;

      Objectdata;

      ComEvent({

    this.id,

        this.data,

      });

    }

    class Event {

    static void sendAppComEvent(BuildContext context, ComEvent event) {

    // BlocProvider.of(context).sendAppComEvent(event);

      }

    static void sendAppEvent(BuildContext context, int id) {

    BlocProvider.of(context).sendAppEvent(id);

      }

    }

    6,database加持

    关于Flutter中数据库问题,我在其前面的文章中已经做过详细介绍,想了解的情移步  -  https://www.jianshu.com/p/072ea0b4513c

    7,common 公共数据存储加持

    1,sp工具类

    import 'dart:convert';

    import 'package:common_utils/common_utils.dart';

    import 'package:flustars/flustars.dart';

    import 'package:flutter_wanandroid/common/common.dart';

    import 'package:flutter_wanandroid/data/protocol/models.dart';

    import 'package:flutter_wanandroid/models/models.dart';

    class SpHelper {

    /// T 用于区分存储类型

      /// Example.

    /// SpHelper.putObject(key, value);

    /// SpHelper.putObject(key, value);

    /// SpHelper.putObject(key, value);

    /// SpHelper.putObject(key, value);

    /// SpHelper.putObject(key, value);

    ///

    /// SpHelper.putObject(key, UserModel);

    ///

      static void putObject(String key, Object value) {

    switch (T) {

    case int:

    SpUtil.putInt(key, value);

    break;

          case double:

    SpUtil.putDouble(key, value);

    break;

          case bool:

    SpUtil.putBool(key, value);

    break;

          case String:

    SpUtil.putString(key, value);

    break;

          case List:

    SpUtil.putStringList(key, value);

    break;

          default:

    SpUtil.putObject(key, value);

    break;

        }

    }

    static ObjectgetObject(String key) {

    Map map = SpUtil.getObject(key);

        if (map ==null)return null;

        Object obj;

        switch (T) {

    case SplashModel:

    obj =SplashModel.fromJson(map);

    break;

          case LanguageModel:

    obj =LanguageModel.fromJson(map);

    break;

          case VersionModel:

    obj =VersionModel.fromJson(map);

    break;

          case UserModel:

    obj =UserModel.fromJson(map);

    break;

          default:

    break;

        }

    return obj;

      }

    static StringgetThemeColor() {

    return SpUtil.getString(Constant.key_theme_color, defValue:'deepPurpleAccent');

      }

    }

    2,一些公用参数
    class Constant {

    static const StringkeyLanguage ='key_language';

      static const intstatus_success =0;

      static const Stringserver_address =wan_android;

      static const Stringwan_android ="https://www.wanandroid.com/";

      static const inttype_sys_update =1;

      static const inttype_refresh_all =5;

      static const Stringkey_theme_color ='key_theme_color';

      static const Stringkey_guide ='key_guide';

      static const Stringkey_splash_model ='key_splash_models';

    }

    class AppConfig {

    static const StringappName ='flutter_wanandroid';

      static const Stringversion ='0.2.2';

      static const boolisDebug =true;

    }

    class LoadStatus {

    static const intfail = -1;

      static const intloading =0;

      static const intsuccess =1;

      static const intempty =2;

    }

    8,状态管理加持

    9,工具utils加持

    import 'package:common_utils/common_utils.dart';

    import 'package:flutter/material.dart';

    import 'package:flutter_wanandroid/common/common.dart';

    import 'package:flutter_wanandroid/res/index.dart';

    import 'package:lpinyin/lpinyin.dart';

    class Utils {

    static StringgetImgPath(String name, {String format:'png'}) {

    return 'assets/images/$name.$format';

      }

    static StringgetPinyin(String str) {

    return PinyinHelper.getShortPinyin(str).substring(0, 1).toUpperCase();

      }

    static ColorgetCircleBg(String str) {

    String pinyin =getPinyin(str);

        return getCircleAvatarBg(pinyin);

      }

    static ColorgetCircleAvatarBg(String key) {

    return circleAvatarMap[key];

      }

    static ColorgetChipBgColor(String name) {

    String pinyin = PinyinHelper.getFirstWordPinyin(name);

        pinyin = pinyin.substring(0, 1).toUpperCase();

        return nameToColor(pinyin);

      }

    static ColornameToColor(String name) {

    // assert(name.length > 1);

        final int hash = name.hashCode &0xffff;

        final double hue = (360.0 * hash / (1 <<15)) %360.0;

        return HSVColor.fromAHSV(1.0, hue, 0.4, 0.90).toColor();

      }

    static StringgetTimeLine(BuildContext context, int timeMillis) {

    //    LogUtil.e("countryCode: " +

    //        Localizations.localeOf(context).countryCode +

    //        "  languageCode: " +

    //        Localizations.localeOf(context).languageCode);

        return TimelineUtil.format(timeMillis,

            locale: Localizations.localeOf(context).languageCode,

            dayFormat: DayFormat.Common);

      }

    static doublegetTitleFontSize(String title) {

    if (ObjectUtil.isEmpty(title) || title.length <10) {

    return 18.0;

        }

    int count =0;

        List list = title.split("");

        for (int i =0, length = list.length; i < length; i++) {

    String ss = list[i];

          if (RegexUtil.isZh(ss)) {

    count++;

          }

    }

    return (count >=10 || title.length >16) ?14.0 :18.0;

      }

    /// 0

    /// -1

    /// 1

      static intgetUpdateStatus(String version) {

    String locVersion = AppConfig.version;

        int remote = int.tryParse(version.replaceAll('.', ''));

        int loc = int.tryParse(locVersion.replaceAll('.', ''));

        if (remote <= loc) {

    return 0;

        }else {

    return (remote - loc >=2) ? -1 :1;

        }

    }

    static boolisNeedLogin(String pageId) {

    if (pageId == Ids.titleCollection) {

    return true;

        }

    return false;

      }

    static intgetLoadStatus(bool hasError, List data) {

    if (hasError)return LoadStatus.fail;

        if (data ==null) {

    return LoadStatus.loading;

        }else if (data.isEmpty) {

    return LoadStatus.empty;

        }else {

    return LoadStatus.success;

        }

    }

    }

    10,UI以及自定义控件

    相关文章

      网友评论

        本文标题:哥哥带你一步步搭建Flutter框架

        本文链接:https://www.haomeiwen.com/subject/pxfmsctx.html