Skip to content
Open
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
6 changes: 6 additions & 0 deletions android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
10 changes: 10 additions & 0 deletions assets/images/automatic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/images/dark_mode.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/images/light_mode.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 13 additions & 1 deletion assets/locales/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -1466,10 +1466,22 @@ msgstr "Your Pro plan was purchased through Google Play. Please manage your subs
msgid "manage_subscription_apple_app_store"
msgstr "Your Pro plan was purchased through the Apple App Store. Please manage your subscription from your Apple ID account settings."


msgid "create_account_error"
msgstr "Create Account Error"

msgid "appearance"
msgstr "Appearance"

msgid "light"
msgstr "Light"

msgid "dark"
msgstr "Dark"

msgid "system"
msgstr "System"





Expand Down
11 changes: 9 additions & 2 deletions lib/core/common/app_asset.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class AppImage extends StatelessWidget {
final AssetType type;
final OnPressed? onPressed;
final BoxFit? fit;
// Set to false for decorative/multicolor assets (illustrations, logos)
// that should not be recolored by the theme.
final bool useThemeColor;

const AppImage({
required this.path,
Expand All @@ -26,22 +29,26 @@ class AppImage extends StatelessWidget {
this.type = AssetType.svg,
this.onPressed,
this.fit,
this.useThemeColor = true,
super.key,
});

@override
Widget build(BuildContext context) {
switch (type) {
case AssetType.svg:
// Use explicit color > theme icon color > no filter (when opted out)
final effectiveColor =
color ?? (useThemeColor ? Theme.of(context).iconTheme.color : null);
return GestureDetector(
onTap: onPressed,
child: SvgPicture.asset(
path,
height: height,
width: width,
fit: fit ?? BoxFit.contain,
colorFilter: color != null
? ColorFilter.mode(color!, BlendMode.srcIn)
colorFilter: effectiveColor != null
? ColorFilter.mode(effectiveColor, BlendMode.srcIn)
: null,
),
);
Expand Down
85 changes: 61 additions & 24 deletions lib/core/common/app_buttons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:lantern/core/common/app_asset.dart';
import 'package:lantern/core/common/app_colors.dart';
import 'package:lantern/core/common/app_dimens.dart';
import 'package:lantern/core/common/app_semantic_colors.dart';
import 'package:lantern/core/common/app_text_styles.dart';
import 'package:lantern/core/common/cap_scaling.dart';

Expand All @@ -21,6 +22,7 @@ class PrimaryButton extends StatelessWidget {
final Color? bgColor;
final Color? textColor;
final bool? isTaller;
final bool? useThemeColor;

// Default constructor for button without an icon
const PrimaryButton({
Expand All @@ -33,6 +35,7 @@ class PrimaryButton extends StatelessWidget {
this.expanded = true,
this.isTaller = false,
this.showBorder = false,
this.useThemeColor,
this.icon,
super.key,
});
Expand All @@ -56,6 +59,7 @@ class PrimaryButton extends StatelessWidget {
path: icon!,
height: iconHeight,
color: iconColor,
useThemeColor: useThemeColor ?? true,
),
label: Text(label),
style: _buildButtonStyle(context, button!, iconSz),
Expand All @@ -69,6 +73,7 @@ class PrimaryButton extends StatelessWidget {
) {
final verticalPad = hCap(context, 12);
final fontSz = spCap(context, 16);
final isDark = Theme.of(context).brightness == Brightness.dark;

// Heights
final minHeight = isTaller == true ? hCap(context, 56) : hCap(context, 48);
Expand All @@ -78,43 +83,50 @@ class PrimaryButton extends StatelessWidget {
backgroundColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.disabled)) {
return AppColors.gray2; // Disabled background color
// primary-disabled-bg: Gray.200 light / Gray.700 dark
return isDark ? AppColors.gray7 : AppColors.gray2;
}
if (states.contains(WidgetState.hovered) &&
bgColor == AppColors.blue1) {
return AppColors.blue2; // Pressed background color
return AppColors.blue2;
}
if (states.contains(WidgetState.hovered)) {
return AppColors.blue8; // Hovered background color
// primary-bg-hover: Blue.800 light / Blue.500 dark
return isDark ? AppColors.blue5 : AppColors.blue8;
}
return bgColor ?? AppColors.blue10; // Default background color
// primary-bg: Blue.1000 light / Blue.600 dark
return bgColor ?? (isDark ? AppColors.blue6 : AppColors.blue10);
},
),
side: WidgetStateProperty.resolveWith<BorderSide>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.disabled)) {
return BorderSide(color: AppColors.gray4, width: 1);
// primary-disabled-border: Gray.400 light / Gray.500 dark
return BorderSide(
color: isDark ? AppColors.gray5 : AppColors.gray4,
width: 1,
);
}
if (showBorder) {
return BorderSide(color: AppColors.gray2, width: 1);
return BorderSide(
color: isDark ? AppColors.gray7 : AppColors.gray2,
width: 1,
);
}
return BorderSide.none;
},
),
// backgroundColor: WidgetStatePropertyAll<Color>(bgColor ?? AppColors.blue7),
iconSize: WidgetStatePropertyAll<double>(iconSz),
padding: WidgetStatePropertyAll<EdgeInsetsGeometry>(
EdgeInsets.symmetric(
vertical: verticalPad,
horizontal: 40.0,
),
EdgeInsets.symmetric(vertical: verticalPad, horizontal: 40.0),
),
textStyle: WidgetStatePropertyAll<TextStyle>(
AppTextStyles.primaryButtonTextStyle.copyWith(
fontSize: expanded ? fontSz : 16.0,
fontWeight: FontWeight.w500,
),
),
// primary-text: Gray.100 both / primary-disabled-text: Gray.500 both
foregroundColor: WidgetStatePropertyAll<Color>(
enabled == false ? AppColors.gray5 : textColor ?? AppColors.gray1,
),
Expand All @@ -138,6 +150,8 @@ class SecondaryButton extends StatelessWidget {

final Color? bgColor;
final bool? isTaller;
final bool? useThemeColor;
final Color? iconColor;

const SecondaryButton({
super.key,
Expand All @@ -148,6 +162,8 @@ class SecondaryButton extends StatelessWidget {
required this.onPressed,
this.icon,
this.bgColor,
this.useThemeColor,
this.iconColor,
});

@override
Expand All @@ -168,6 +184,8 @@ class SecondaryButton extends StatelessWidget {
icon: AppImage(
path: icon!,
height: iconHeight,
color: iconColor,
useThemeColor: useThemeColor ?? false,
),
label: Text(label),
style: _buildButtonStyle(context, button!, iconSz),
Expand All @@ -181,40 +199,58 @@ class SecondaryButton extends StatelessWidget {
) {
final verticalPad = hCap(context, 12);
final fontSz = spCap(context, 16);
final isDark = Theme.of(context).brightness == Brightness.dark;

// Heights
final height = isTaller == true ? hCap(context, 56) : hCap(context, 50);

// secondary-text: Gray.900 light / Gray.100 dark
final textColor = isDark ? AppColors.gray1 : AppColors.gray9;

return style.copyWith(
backgroundColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.disabled)) {
return AppColors.gray2; // Disabled background color
// secondary-disabled-bg: Gray.200 light / Gray.900 dark
return isDark ? AppColors.gray9 : AppColors.gray2;
}
return bgColor ?? AppColors.gray1; // Default background color
if (states.contains(WidgetState.hovered)) {
// secondary-bg-hover: Gray.200 light / Gray.800 dark
return isDark ? AppColors.gray8 : AppColors.gray2;
}
// secondary-bg: Gray.100 light / Gray.900 dark
return bgColor ?? (isDark ? AppColors.gray9 : AppColors.gray1);
},
),
side: WidgetStateProperty.resolveWith<BorderSide>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.disabled)) {
return BorderSide(color: AppColors.gray4, width: 1);
// secondary-disabled-border: Gray.400 light / Gray.700 dark
return BorderSide(
color: isDark ? AppColors.gray7 : AppColors.gray4,
width: 1,
);
}
return BorderSide(color: AppColors.gray4, width: 1);
// secondary-border: Gray.500 light / Gray.600 dark
return BorderSide(
color: isDark ? AppColors.gray6 : AppColors.gray5,
width: 1,
);
},
),
overlayColor: WidgetStatePropertyAll<Color>(AppColors.gray2),
foregroundColor: WidgetStatePropertyAll<Color>(AppColors.gray9),
// secondary-bg-hover used as overlay
overlayColor: WidgetStatePropertyAll<Color>(
isDark ? AppColors.gray8 : AppColors.gray2,
),
foregroundColor: WidgetStatePropertyAll<Color>(textColor),
iconSize: WidgetStatePropertyAll<double>(iconSz),
padding: WidgetStatePropertyAll<EdgeInsetsGeometry>(
EdgeInsets.symmetric(
vertical: verticalPad,
horizontal: 40.0,
),
EdgeInsets.symmetric(vertical: verticalPad, horizontal: 40.0),
),
textStyle: WidgetStatePropertyAll<TextStyle>(
AppTextStyles.primaryButtonTextStyle.copyWith(
fontSize: expanded ? fontSz : 16.0,
color: AppColors.gray9,
color: textColor,
fontWeight: FontWeight.w600,
),
),
Expand Down Expand Up @@ -259,7 +295,8 @@ class AppTextButton extends StatelessWidget {
underLine ? TextDecoration.underline : TextDecoration.none,
fontSize: cappedFontSize,
),
foregroundColor: textColor ?? AppColors.blue7,
// text.link from semantic token
foregroundColor: textColor ?? context.textLink,
),
child: Text(label),
);
Expand Down Expand Up @@ -306,14 +343,14 @@ class AppRadioButton<T> extends StatelessWidget {
@override
Widget build(BuildContext context) {
final sz = hCap(context, 24);

return SizedBox(
width: sz,
height: sz,
child: Radio<T>(
value: value,
groupValue: groupValue,
onChanged: onChanged,
activeColor: context.textLink,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact,
),
Expand Down
Loading
Loading