Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release

test/hive
13 changes: 12 additions & 1 deletion lib/apis/passport_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class PassportAPI {
static const String _api = '${Urls.apiHost}/passport';
static const String _user = '$_api/user';

static Future<ResponseModel<EmptyDataModel>> login(String u, String p) {
/// Login failed:
/// {data: {captcha: , desc_url: , description: 帐号或密码错误, error_code: 1009}, message: error}
static Future<ResponseModel<UserPassportModel>> login(String u, String p) {
return HttpUtil.fetchModel(
FetchType.post,
url: '$_user/login/',
Expand All @@ -33,4 +35,13 @@ class PassportAPI {
contentType: Headers.formUrlEncodedContentType,
);
}

/// TODO(shirne): refresh token
static Future<ResponseModel<UserPassportModel>> restore() {
return HttpUtil.fetchModel(
FetchType.post,
url: '$_user/refresh/',
contentType: Headers.formUrlEncodedContentType,
);
}
}
61 changes: 32 additions & 29 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:oktoast/oktoast.dart';

import 'exports.dart';
Expand Down Expand Up @@ -73,38 +74,40 @@ class JJAppState extends State<JJApp> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
return _buildOKToast(
child: MaterialApp(
theme: themeBy(brightness: Brightness.light),
darkTheme: themeBy(brightness: Brightness.dark),
initialRoute: Routes.splashPage.name,
navigatorKey: JJ.navigatorKey,
navigatorObservers: <NavigatorObserver>[
JJ.routeObserver,
JJNavigatorObserver(),
],
onGenerateTitle: (BuildContext c) => c.l10n.appTitle,
onGenerateRoute: (RouteSettings settings) => onGenerateRoute(
settings: settings,
getRouteSettings: getRouteSettings,
notFoundPageBuilder: () => Container(
alignment: Alignment.center,
color: Colors.black,
child: Text(
context.l10n.exceptionRouteNotFound(
settings.name ?? context.l10n.exceptionRouteUnknown,
child: ProviderScope(
child: MaterialApp(
theme: themeBy(brightness: Brightness.light),
darkTheme: themeBy(brightness: Brightness.dark),
initialRoute: Routes.splashPage.name,
navigatorKey: JJ.navigatorKey,
navigatorObservers: <NavigatorObserver>[
JJ.routeObserver,
JJNavigatorObserver(),
],
onGenerateTitle: (BuildContext c) => c.l10n.appTitle,
onGenerateRoute: (RouteSettings settings) => onGenerateRoute(
settings: settings,
getRouteSettings: getRouteSettings,
notFoundPageBuilder: () => Container(
alignment: Alignment.center,
color: Colors.black,
child: Text(
context.l10n.exceptionRouteNotFound(
settings.name ?? context.l10n.exceptionRouteUnknown,
),
style: const TextStyle(color: Colors.white, inherit: false),
),
style: const TextStyle(color: Colors.white, inherit: false),
),
),
),
localizationsDelegates: JJLocalizations.localizationsDelegates,
supportedLocales: JJLocalizations.supportedLocales,
scrollBehavior: _ScrollBehavior(),
builder: (BuildContext context, Widget? child) => Stack(
children: <Widget>[
_buildAnnotatedRegion(context, child!),
_buildBottomPaddingVerticalShield(context),
],
localizationsDelegates: JJLocalizations.localizationsDelegates,
supportedLocales: JJLocalizations.supportedLocales,
scrollBehavior: _ScrollBehavior(),
builder: (BuildContext context, Widget? child) => Stack(
children: <Widget>[
_buildAnnotatedRegion(context, child!),
_buildBottomPaddingVerticalShield(context),
],
),
),
),
);
Expand Down
70 changes: 70 additions & 0 deletions lib/constants/providers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../models/data_model.dart';
import '../models/repositories.dart';
import '../utils/hive_util.dart';

const _tokenKey = 'token';

class TokenNotifier extends StateNotifier<UserAuthen> {
TokenNotifier(super.state);

bool get isLogin => state.token.isNotEmpty;
String get token => state.token;

void restore() {
final data = HiveUtil.box.get(_tokenKey);
if (data != null) {
state = data;
}
}

void update(String token) {
state = UserAuthen(
token: token,
expireIn: 7200,
timestamp: DateTime.now().millisecond,
);
saveState();
}

void logout() {
state = UserAuthen();
saveState();
}

void saveState() {
if (state.token.isEmpty) {
HiveUtil.box.delete(_tokenKey);
} else {
HiveUtil.box.put(_tokenKey, state);
}
}
}

class UserPassportNotifier extends StateNotifier<UserPassportModel> {
UserPassportNotifier(super.state);

void update(UserPassportModel data) {
state = data;
}
}

final tokenProvider = StateNotifierProvider<TokenNotifier, UserAuthen>((ref) {
final userPassport = ref.watch(userProvider);
if (userPassport.isEmpty) {
return TokenNotifier(UserAuthen());
}
return TokenNotifier(
UserAuthen(
token: userPassport.sessionKey,
expireIn: 7200,
timestamp: DateTime.now().millisecond,
),
)..saveState();
});

final userProvider =
StateNotifierProvider<UserPassportNotifier, UserPassportModel>((ref) {
return UserPassportNotifier(const UserPassportModel.empty());
});
21 changes: 20 additions & 1 deletion lib/constants/themes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,28 @@ ThemeData themeBy({
inputDecorationTheme: InputDecorationTheme(
border: InputBorder.none,
enabledBorder: InputBorder.none,
hintStyle: TextStyle(color: tg.iconColor),
hintStyle: TextStyle(color: tg.iconColor, height: 1.35),
prefixIconColor: tg.iconColor,
),
scaffoldBackgroundColor: tg.backgroundColor,
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: themeColorDark,
shape: const StadiumBorder(),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: themeColorDark.withAlpha(200),
shape: const StadiumBorder(),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: themeColorDark.withAlpha(200),
shape: const StadiumBorder(),
),
),
textSelectionTheme: TextSelectionThemeData(
cursorColor: tg.themeColor,
selectionColor: tg.themeColor.withOpacity(.25),
Expand Down
3 changes: 3 additions & 0 deletions lib/exports.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export 'apis/passport_api.dart';
export 'apis/recommend_api.dart';
export 'apis/tag_api.dart';
export 'constants/constants.dart';
export 'constants/providers.dart';
export 'constants/resources.dart';
export 'constants/screens.dart';
export 'constants/styles.dart';
Expand All @@ -30,12 +31,14 @@ export 'internals/urls.dart';
export 'l10n/gen/jj_localizations.dart';
export 'models/data_model.dart';
export 'models/loading_base.dart';
export 'models/repositories.dart';
export 'models/response_model.dart';
export 'routes/juejin_routes.dart';
export 'routes/page_route.dart';
export 'utils/cache_util.dart';
export 'utils/device_util.dart';
export 'utils/haptic_util.dart';
export 'utils/hive_util.dart';
export 'utils/http_util.dart';
export 'utils/log_util.dart';
export 'utils/package_util.dart';
Expand Down
10 changes: 10 additions & 0 deletions lib/extensions/string_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

import 'package:flutter/widgets.dart' show Characters;

final mobileRegExp = RegExp(r'^1[3-9]\d{9}$');
final emailRegExp = RegExp(
r'^[a-z][0-9a-z\-_ \.]*@([a-z0-9]+\.)+?[a-z]+$',
caseSensitive: false,
);

extension StringExtension on String {
String get notBreak => Characters(this).join('\u{200B}');

Expand All @@ -15,6 +21,10 @@ extension StringExtension on String {

String removeFirst(Pattern pattern, [int startIndex = 0]) =>
replaceFirst(pattern, '', startIndex);

bool isMobile() => mobileRegExp.hasMatch(this);

bool isEmail() => emailRegExp.hasMatch(this);
}

extension NullableStringExtension on String? {
Expand Down
14 changes: 14 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@
"sortRecommend": "Recommend",
"sortLatest": "Latest",

"loginTitle": "Sign in",
"loginSlogan": "Log in to experience more",
"hintUsername":"Phone/email",
"hintPassword":"Password",
"buttonSignIn":"Sign in",
"linkSignUp":"Create an account",
"linkRetrieve":"Fogot password?",

"loginSuccess": "Login success",

"needUsername": "Please enter a mobile number or email",
"incorectUsername": "Username must be a mobile number or email",
"needPassword": "Please enter your password",

"durationYears": "{many, plural, =1{1 year} other{{many} years}} ago",
"@durationYears": {
"placeholders": {"many": {"type": "int"}}
Expand Down
14 changes: 14 additions & 0 deletions lib/l10n/app_zh.arb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@

"join": "加入",

"loginSuccess": "登录成功",

"loginTitle": "登录",
"loginSlogan": "登录体验更多精彩",
"hintUsername":"手机号/邮箱",
"hintPassword":"密码",
"buttonSignIn":"登录",
"linkSignUp":"注册新用户",
"linkRetrieve":"找回密码",

"needUsername": "请填写用户名",
"incorectUsername": "请填写手机号码或者邮箱",
"needPassword": "请填写密码",

"durationYears": "{many, plural, other{{many}年前}}",
"durationMonths": "{many, plural, other{{many}月前}}",
"durationDays": "{many, plural, other{{many}天前}}",
Expand Down
66 changes: 66 additions & 0 deletions lib/l10n/gen/jj_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,72 @@ abstract class JJLocalizations {
/// **'Latest'**
String get sortLatest;

/// No description provided for @loginTitle.
///
/// In en, this message translates to:
/// **'Sign in'**
String get loginTitle;

/// No description provided for @loginSlogan.
///
/// In en, this message translates to:
/// **'Log in to experience more'**
String get loginSlogan;

/// No description provided for @hintUsername.
///
/// In en, this message translates to:
/// **'Phone/email'**
String get hintUsername;

/// No description provided for @hintPassword.
///
/// In en, this message translates to:
/// **'Password'**
String get hintPassword;

/// No description provided for @buttonSignIn.
///
/// In en, this message translates to:
/// **'Sign in'**
String get buttonSignIn;

/// No description provided for @linkSignUp.
///
/// In en, this message translates to:
/// **'Create an account'**
String get linkSignUp;

/// No description provided for @linkRetrieve.
///
/// In en, this message translates to:
/// **'Fogot password?'**
String get linkRetrieve;

/// No description provided for @loginSuccess.
///
/// In en, this message translates to:
/// **'Login success'**
String get loginSuccess;

/// No description provided for @needUsername.
///
/// In en, this message translates to:
/// **'Please enter a mobile number or email'**
String get needUsername;

/// No description provided for @incorectUsername.
///
/// In en, this message translates to:
/// **'Username must be a mobile number or email'**
String get incorectUsername;

/// No description provided for @needPassword.
///
/// In en, this message translates to:
/// **'Please enter your password'**
String get needPassword;

/// No description provided for @durationYears.
///
/// In en, this message translates to:
Expand Down
Loading