Skip to content

Commit

Permalink
Merge pull request #69 from KhalidWar/dev
Browse files Browse the repository at this point in the history
What's new:
1. Fixed offline data 
2. Fixed several UI bug.
3. Overhauled Offline data caching mechanism.
4. Improved date and time format.
5. Improved error messages.
6. Several under the hood improvements.
7. Too many other improvements to list.
  • Loading branch information
KhalidWar authored Nov 13, 2022
2 parents 5b892b2 + 2fd0d31 commit e041a15
Show file tree
Hide file tree
Showing 167 changed files with 3,120 additions and 2,958 deletions.
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
}

android {
compileSdkVersion 32
compileSdkVersion 33

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
Expand All @@ -45,7 +45,7 @@ android {
defaultConfig {
applicationId "com.khalidwar.anonaddy"
minSdkVersion 27
targetSdkVersion 32
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
Expand Down
2 changes: 1 addition & 1 deletion lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:anonaddy/route_generator.dart';
import 'package:anonaddy/screens/authorization_screen/authorization_screen.dart';
import 'package:anonaddy/services/theme/theme.dart';
import 'package:anonaddy/shared_components/constants/app_strings.dart';
import 'package:anonaddy/state_management/settings/settings_notifier.dart';
import 'package:anonaddy/notifiers/settings/settings_notifier.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Expand Down
25 changes: 0 additions & 25 deletions lib/global_providers.dart

This file was deleted.

4 changes: 2 additions & 2 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:anonaddy/app.dart';
import 'package:anonaddy/global_providers.dart';
import 'package:anonaddy/utilities/utilities_export.dart';
import 'package:anonaddy/services/data_storage/offline_data_storage.dart';
import 'package:anonaddy/utilities/startup_methods.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
Expand Down
4 changes: 2 additions & 2 deletions lib/models/failed_delivery/failed_delivery.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ class FailedDelivery {
@JsonKey(name: 'recipient_email')
final String? recipientEmail;
@JsonKey(name: 'alias_id')
final String aliasId;
final String? aliasId;
@JsonKey(name: 'alias_email')
final String aliasEmail;
final String? aliasEmail;
@JsonKey(name: 'bounce_type')
final String bounceType;
@JsonKey(name: 'remote_mta')
Expand Down
4 changes: 2 additions & 2 deletions lib/models/failed_delivery/failed_delivery.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 83 additions & 0 deletions lib/notifiers/account/account_notifier.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import 'package:anonaddy/notifiers/account/account_state.dart';
import 'package:anonaddy/services/account/account_service.dart';
import 'package:anonaddy/utilities/utilities.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final accountStateNotifier =
StateNotifierProvider<AccountNotifier, AccountState>((ref) {
return AccountNotifier(accountService: ref.read(accountServiceProvider));
});

class AccountNotifier extends StateNotifier<AccountState> {
AccountNotifier({
required this.accountService,
AccountState? initialState,
}) : super(initialState ?? AccountState.initialState());

final AccountService accountService;

/// Updates UI to the newState
void _updateState(AccountState newState) {
if (mounted) state = newState;
}

Future<void> fetchAccount() async {
try {
_updateState(state.copyWith(status: AccountStatus.loading));

final account = await accountService.fetchAccount();

/// Construct new state
final newState =
state.copyWith(status: AccountStatus.loaded, account: account);

/// Update UI with the latest state
_updateState(newState);
} catch (error) {
_updateState(state.copyWith(
status: AccountStatus.failed,
errorMessage: error.toString(),
));

/// Retry after an error
_retryOnError();
}
}

/// Silently fetches the latest account data and displays them
Future<void> refreshAccount() async {
try {
/// Only trigger fetch API when app is Foreground to avoid API spamming
final account = await accountService.fetchAccount();

/// Update UI with the latest state
_updateState(state.copyWith(
status: AccountStatus.loaded,
account: account,
));
} catch (error) {
Utilities.showToast(error.toString());
}
}

/// Triggers [fetchAccount] when it fails after a certain amount of time
Future _retryOnError() async {
if (state.isFailed) {
await Future.delayed(const Duration(seconds: 5));
await fetchAccount();
}
}

/// Loads [Account] data from disk
Future<void> loadAccountFromDisk() async {
try {
final account = await accountService.loadAccountFromDisk();
_updateState(state.copyWith(
status: AccountStatus.loaded,
account: account,
));
} catch (_) {
return;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'package:anonaddy/models/account/account.dart';
import 'package:anonaddy/shared_components/constants/anonaddy_string.dart';
import 'package:anonaddy/shared_components/constants/app_strings.dart';

enum AccountStatus { loading, loaded, failed }

Expand All @@ -19,7 +18,7 @@ class AccountState {
return AccountState(
status: AccountStatus.loading,
account: Account(),
errorMessage: AppStrings.somethingWentWrong,
errorMessage: '',
);
}

Expand All @@ -35,22 +34,6 @@ class AccountState {
);
}

Map<String, dynamic> toMap() {
return {
'status': status,
'account': account,
'errorMessage': errorMessage,
};
}

factory AccountState.fromMap(Map<String, dynamic> map) {
return AccountState(
status: map['status'] as AccountStatus,
account: map['account'] as Account,
errorMessage: map['errorMessage'] as String,
);
}

@override
String toString() {
return 'AccountState{status: $status, account: $account, errorMessage: $errorMessage}';
Expand Down
163 changes: 163 additions & 0 deletions lib/notifiers/alias_state/alias_screen_notifier.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import 'package:anonaddy/models/alias/alias.dart';
import 'package:anonaddy/notifiers/alias_state/alias_screen_state.dart';
import 'package:anonaddy/notifiers/alias_state/alias_tab_notifier.dart';
import 'package:anonaddy/services/alias/alias_service.dart';
import 'package:anonaddy/shared_components/constants/app_strings.dart';
import 'package:anonaddy/shared_components/constants/toast_message.dart';
import 'package:anonaddy/utilities/utilities.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final aliasScreenStateNotifier =
StateNotifierProvider.autoDispose<AliasScreenNotifier, AliasScreenState>(
(ref) {
final aliasTab = ref.read(aliasTabStateNotifier.notifier);
ref.onDispose(() => aliasTab.refreshAliases());

return AliasScreenNotifier(
aliasService: ref.read(aliasServiceProvider),
aliasTabNotifier: aliasTab,
);
});

class AliasScreenNotifier extends StateNotifier<AliasScreenState> {
AliasScreenNotifier({
required this.aliasService,
required this.aliasTabNotifier,
AliasScreenState? initialState,
}) : super(initialState ?? AliasScreenState.initialState());

final AliasService aliasService;
final AliasTabNotifier aliasTabNotifier;

/// Update AliasScreen state
void _updateState(AliasScreenState newState) {
if (mounted) state = newState;
}

Future<void> fetchSpecificAlias(String aliasId) async {
try {
/// Initially set AliasScreen to loading
_updateState(state.copyWith(status: AliasScreenStatus.loading));

final updatedAlias = await aliasService.fetchSpecificAlias(aliasId);

/// Assign newly fetched alias data to AliasScreen state
final newState =
state.copyWith(status: AliasScreenStatus.loaded, alias: updatedAlias);
_updateState(newState);
} catch (error) {
final newState = state.copyWith(
status: AliasScreenStatus.failed,
errorMessage: error.toString(),
);
_updateState(newState);
}
}

Future<void> editDescription(String newDesc) async {
try {
final updatedAlias =
await aliasService.updateAliasDescription(state.alias.id, newDesc);
Utilities.showToast(ToastMessage.editDescriptionSuccess);
_updateState(state.copyWith(alias: updatedAlias));
} catch (error) {
Utilities.showToast(error.toString());
}
}

Future<void> deactivateAlias() async {
try {
_updateState(state.copyWith(isToggleLoading: true));
await aliasService.deactivateAlias(state.alias.id);
final updatedAlias = state.alias.copyWith(active: false);
_updateState(state.copyWith(isToggleLoading: false, alias: updatedAlias));
} catch (error) {
Utilities.showToast(error.toString());
_updateState(state.copyWith(isToggleLoading: false));
}
}

Future<void> activateAlias() async {
try {
_updateState(state.copyWith(isToggleLoading: true));
final newAlias = await aliasService.activateAlias(state.alias.id);
final updateAlias = state.alias.copyWith(active: newAlias.active);
_updateState(state.copyWith(isToggleLoading: false, alias: updateAlias));
} catch (error) {
Utilities.showToast(error.toString());
_updateState(state.copyWith(isToggleLoading: false));
}
}

Future<void> deleteAlias(Alias alias) async {
try {
_updateState(state.copyWith(deleteAliasLoading: true));
await aliasService.deleteAlias(alias.id);
Utilities.showToast(ToastMessage.deleteAliasSuccess);
final updatedAlias = state.alias.copyWith(deletedAt: '');
aliasTabNotifier.removeDeletedAlias(alias);

final newState =
state.copyWith(deleteAliasLoading: false, alias: updatedAlias);
_updateState(newState);
} catch (error) {
Utilities.showToast(error.toString());
_updateState(state.copyWith(deleteAliasLoading: false));
rethrow;
}
}

Future<void> restoreAlias(Alias alias) async {
try {
_updateState(state.copyWith(deleteAliasLoading: true));
final newAlias = await aliasService.restoreAlias(alias.id);
Utilities.showToast(ToastMessage.restoreAliasSuccess);
aliasTabNotifier.removeRestoredAlias(alias);

final newState =
state.copyWith(deleteAliasLoading: false, alias: newAlias);
_updateState(newState);
} catch (error) {
Utilities.showToast(error.toString());
_updateState(state.copyWith(deleteAliasLoading: false));
}
}

Future<void> updateAliasDefaultRecipient(List<String> recipients) async {
try {
_updateState(state.copyWith(updateRecipientLoading: true));
final updatedAlias = await aliasService.updateAliasDefaultRecipient(
state.alias.id, recipients);
final newState =
state.copyWith(updateRecipientLoading: false, alias: updatedAlias);
_updateState(newState);
} catch (error) {
Utilities.showToast(error.toString());
_updateState(state.copyWith(updateRecipientLoading: false));
}
}

Future<void> forgetAlias() async {
try {
await aliasService.forgetAlias(state.alias.id);
Utilities.showToast(ToastMessage.forgetAliasSuccess);
} catch (error) {
Utilities.showToast(error.toString());
}
}

Future<void> sendFromAlias(String destinationEmail) async {
try {
/// https://anonaddy.com/help/sending-email-from-an-alias/
final leftPartOfAlias = state.alias.email.split('@')[0];
final rightPartOfAlias = state.alias.email.split('@')[1];
final recipientEmail = destinationEmail.replaceAll('@', '=');
final generatedAddress =
'$leftPartOfAlias+$recipientEmail@$rightPartOfAlias';
await Utilities.copyOnTap(generatedAddress);
Utilities.showToast(ToastMessage.sendFromAliasSuccess);
} catch (error) {
Utilities.showToast(AppStrings.somethingWentWrong);
}
}
}
Loading

0 comments on commit e041a15

Please sign in to comment.