diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md
index 111bacf..e75317f 100644
--- a/.github/ISSUE_TEMPLATE/---bug-report.md
+++ b/.github/ISSUE_TEMPLATE/---bug-report.md
@@ -24,6 +24,14 @@ A clear and concise description of what the bug is.
3. Scroll down to '....'
4. See error
+### Relevant logs
+
+
+ Logs
+
+ Paste your logs here. Logs can be found in lemmur: settings > about lemmur > logs.
+
+
### Expected behavior
A clear and concise description of what you expected to happen.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 119f47b..1943a7f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,16 +7,12 @@ on:
branches: [master]
jobs:
- android:
- name: Android
+ lint:
+ name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- - uses: actions/setup-java@v1
- with:
- java-version: "12.x"
-
- uses: subosito/flutter-action@v1
with:
channel: "stable"
@@ -33,12 +29,51 @@ jobs:
- name: Run tests
run: flutter test
+ android:
+ name: Android
+ runs-on: ubuntu-latest
+ needs: lint
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: actions/setup-java@v1
+ with:
+ java-version: "12.x"
+
+ - uses: subosito/flutter-action@v1
+ with:
+ channel: "stable"
+
+ - name: Inject keystore
+ working-directory: android/app
+ run: |
+ echo "${{ secrets.SIGNING_KEY }}" | base64 -d | tee key.jks >/dev/null
+
- name: Android build
- run: flutter build apk --split-per-abi
+ env:
+ ANDROID_KEY_ALIAS: ${{ secrets.ALIAS }}
+ ANDROID_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
+ ANDROID_STORE_PATH: key.jks
+ ANDROID_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
+ run: flutter build apk --split-per-abi --release --target lib/main_prod.dart --flavor prod
+
+ ios:
+ name: iOS
+ runs-on: macos-latest
+ needs: lint
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: subosito/flutter-action@v1
+ with:
+ channel: "stable"
+
+ - run: flutter build ios --no-codesign --release --target lib/main_prod.dart --flavor prod
linux:
name: Linux
runs-on: ubuntu-latest
+ needs: lint
steps:
- uses: actions/checkout@v2
@@ -57,11 +92,12 @@ jobs:
- name: Build
run: |
- flutter build linux
+ flutter build linux --release --target lib/main_prod.dart
windows:
name: Windows
runs-on: windows-latest
+ needs: lint
steps:
- uses: actions/checkout@v2
@@ -75,4 +111,4 @@ jobs:
- name: Build
run: |
- flutter build windows
+ flutter build windows --release --target lib/main_prod.dart
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index dcaedc4..ee31602 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -37,36 +37,36 @@ jobs:
run: flutter pub get
- name: Inject keystore
- working-directory: android
- env:
- KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
- KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
- ALIAS: ${{ secrets.ALIAS }}
- SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
+ working-directory: android/app
run: |
- echo storePassword=$KEY_STORE_PASSWORD > key.properties
- echo keyPassword=$KEY_PASSWORD >> key.properties
- echo keyAlias=$ALIAS >> key.properties
- echo storeFile=$HOME/key.jks >> key.properties
-
- echo $SIGNING_KEY | base64 -d | tee ~/key.jks >/dev/null
+ echo "${{ secrets.SIGNING_KEY }}" | base64 -d | tee key.jks >/dev/null
- name: Generate appbundle
- run: flutter build appbundle
+ env:
+ ANDROID_KEY_ALIAS: ${{ secrets.ALIAS }}
+ ANDROID_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
+ ANDROID_STORE_PATH: key.jks
+ ANDROID_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
+ run: flutter build appbundle --release --target lib/main_prod.dart --flavor prod
- uses: actions/upload-artifact@v2
with:
name: android-appbundle
path: |
- build/app/outputs/bundle/release/app-release.aab
+ build/app/outputs/bundle/prodRelease/app-prod-release.aab
- name: Android build
- run: |
- flutter build apk --split-per-abi
+ env:
+ ANDROID_KEY_ALIAS: ${{ secrets.ALIAS }}
+ ANDROID_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
+ ANDROID_STORE_PATH: key.jks
+ ANDROID_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
+ run:
+ flutter build apk --split-per-abi --release --target lib/main_prod.dart --flavor prod
- mv build/app/outputs/flutter-apk/app-arm64-v8a-release.apk lemmur-${{ needs.get-vars.outputs.tag }}-arm64-v8a-android.apk
- mv build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk lemmur-${{ needs.get-vars.outputs.tag }}-armeabi-v7a-android.apk
- mv build/app/outputs/flutter-apk/app-x86_64-release.apk lemmur-${{ needs.get-vars.outputs.tag }}-x86_64-android.apk
+ mv build/app/outputs/flutter-apk/app-arm64-v8a-prod-release.apk lemmur-${{ needs.get-vars.outputs.tag }}-arm64-v8a-android.apk
+ mv build/app/outputs/flutter-apk/app-armeabi-v7a-prod-release.apk lemmur-${{ needs.get-vars.outputs.tag }}-armeabi-v7a-android.apk
+ mv build/app/outputs/flutter-apk/app-x86_64-prod-release.apk lemmur-${{ needs.get-vars.outputs.tag }}-x86_64-android.apk
- uses: actions/upload-artifact@v2
with:
@@ -96,17 +96,17 @@ jobs:
- name: Build
run: |
- flutter build linux
+ flutter build linux --release --target lib/main_prod.dart
- name: Archive
- working-directory: build/linux/release/bundle
+ working-directory: build/linux/x64/release/bundle
run: |
tar -czf lemmur-${{ needs.get-vars.outputs.tag }}-x86_64-linux.tar.gz *
- uses: actions/upload-artifact@v2
with:
name: linux-build
- path: build/linux/release/bundle/lemmur-*.tar.gz
+ path: build/linux/x64/release/bundle/lemmur-*.tar.gz
windows-build:
name: Windows build
@@ -125,7 +125,7 @@ jobs:
- name: Build
run: |
- flutter build windows
+ flutter build windows --release --target lib/main_prod.dart
- name: Archive
working-directory: build/windows/runner/Release
diff --git a/.gitignore b/.gitignore
index edbaef4..ea78c22 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,3 +36,6 @@ app.*.map.json
# Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
+
+# Xcode build files
+ios/build
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 0e466ae..f9174c8 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -4,19 +4,25 @@
{
"name": "Debug",
"request": "launch",
- "type": "dart"
+ "type": "dart",
+ "program": "lib/main_dev.dart",
+ "args": ["--flavor", "dev"]
},
{
"name": "Profile",
"request": "launch",
"type": "dart",
- "flutterMode": "profile"
+ "flutterMode": "profile",
+ "program": "lib/main_dev.dart",
+ "args": ["--flavor", "dev"]
},
{
"name": "Release",
"request": "launch",
"type": "dart",
- "flutterMode": "release"
+ "flutterMode": "release",
+ "program": "lib/main_dev.dart",
+ "args": ["--flavor", "dev"]
}
]
}
diff --git a/.vscode/lemmur.code-snippets b/.vscode/lemmur.code-snippets
index 8f926b5..6f05ad0 100644
--- a/.vscode/lemmur.code-snippets
+++ b/.vscode/lemmur.code-snippets
@@ -29,5 +29,20 @@
"L10n string": {
"prefix": "l10n",
"body": ["L10n.of(context)!.$0"]
+ },
+ "Mobx store": {
+ "prefix": "mobxstore",
+ "body": [
+ "import 'package:mobx/mobx.dart';",
+ "",
+ "part '$TM_FILENAME_BASE.g.dart';",
+ "",
+ "class ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/g} = _${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/g} with _$${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/g};",
+ "",
+ "abstract class _${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/g} with Store {",
+ "\t@observable",
+ "\t$0",
+ "}"
+ ]
}
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8976339..6b34b9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## Unreleased
+
+### Added
+
+- Logging: local logs about some actions/errors. Can be accessed from **settings > about lemmur > logs**
+
## v0.6.0 - 2021-09-06
### Added
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 3355edd..3bb119f 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -8,6 +8,7 @@ linter:
- avoid_init_to_null
- avoid_null_checks_in_equality_operators
- avoid_positional_boolean_parameters
+ - avoid_print
- avoid_private_typedef_functions
- avoid_redundant_argument_values
- avoid_relative_lib_imports
diff --git a/android/app/build.gradle b/android/app/build.gradle
index eef128a..081e300 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -25,12 +25,6 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
-def keystoreProperties = new Properties()
-def keystorePropertiesFile = rootProject.file('key.properties')
-if (keystorePropertiesFile.exists()) {
- keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
-}
-
android {
compileSdkVersion 30
@@ -51,21 +45,55 @@ android {
}
signingConfigs {
- release {
- keyAlias keystoreProperties['keyAlias']
- keyPassword keystoreProperties['keyPassword']
- storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
- storePassword keystoreProperties['storePassword']
+ dev { }
+
+ if (System.getenv("ANDROID_STORE_PATH")) {
+ prod {
+ keyAlias System.getenv("ANDROID_KEY_ALIAS")
+ keyPassword System.getenv("ANDROID_KEY_PASSWORD")
+ storeFile file(System.getenv("ANDROID_STORE_PATH"))
+ storePassword System.getenv("ANDROID_STORE_PASSWORD")
+ }
+ } else {
+ prod { }
}
- }
+ }
+
+ flavorDimensions "app"
+
+ productFlavors {
+ dev {
+ dimension "app"
+ applicationIdSuffix ".dev"
+ versionNameSuffix "-dev"
+ manifestPlaceholders = [
+ appName: "lemmur DEV"
+ ]
+ signingConfig signingConfigs.debug
+ }
+
+ prod {
+ dimension "app"
+ manifestPlaceholders = [
+ appName: "lemmur"
+ ]
+ signingConfig signingConfigs.prod
+ }
+ }
buildTypes {
+ debug {
+ testCoverageEnabled true
+ debuggable true
+ minifyEnabled false
+ signingConfig null
+ }
+
release {
- if (keystorePropertiesFile.exists()) {
- signingConfig signingConfigs.release
- } else {
- signingConfig signingConfigs.debug
- }
+ debuggable false
+ minifyEnabled true
+ shrinkResources false
+ zipAlignEnabled true
}
}
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 76dfcf3..c5516c9 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -11,7 +11,7 @@
+ location = "self:">
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/dev.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/dev.xcscheme
new file mode 100644
index 0000000..b547afd
--- /dev/null
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/dev.xcscheme
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/prod.xcscheme
similarity index 92%
rename from ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
rename to ios/Runner.xcodeproj/xcshareddata/xcschemes/prod.xcscheme
index fb2dffc..c0b004d 100644
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/prod.xcscheme
@@ -1,6 +1,6 @@
@@ -40,7 +40,7 @@
+ buildConfiguration = "Debug-prod">
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 18954dc..2b09730 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -1,53 +1,55 @@
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- lemmur
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- $(FLUTTER_BUILD_NAME)
- CFBundleSignature
- ????
- CFBundleVersion
- $(FLUTTER_BUILD_NUMBER)
- LSRequiresIPhoneOS
-
- UILaunchStoryboardName
- LaunchScreen
- UIMainStoryboardFile
- Main
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UIViewControllerBasedStatusBarAppearance
-
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(BUNDLE_NAME)
+ CFBundleDisplayName
+ $(BUNDLE_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
-
- NSPhotoLibraryUsageDescription
- For uploading images for posts/avatars
- NSCameraUsageDescription
- For uploading images for posts/avatars
- NSMicrophoneUsageDescription
- For recording videos for posts
-
+
+ NSPhotoLibraryUsageDescription
+ For uploading images for posts/avatars
+ NSCameraUsageDescription
+ For uploading images for posts/avatars
+ NSMicrophoneUsageDescription
+ For recording videos for posts
+
diff --git a/lib/app.dart b/lib/app.dart
new file mode 100644
index 0000000..5944e4d
--- /dev/null
+++ b/lib/app.dart
@@ -0,0 +1,30 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:keyboard_dismisser/keyboard_dismisser.dart';
+
+import 'hooks/stores.dart';
+import 'l10n/l10n.dart';
+import 'pages/home_page.dart';
+import 'theme.dart';
+
+class MyApp extends HookWidget {
+ const MyApp();
+
+ @override
+ Widget build(BuildContext context) {
+ final configStore = useConfigStore();
+
+ return KeyboardDismisser(
+ child: MaterialApp(
+ title: 'lemmur',
+ supportedLocales: L10n.supportedLocales,
+ localizationsDelegates: L10n.localizationsDelegates,
+ themeMode: configStore.theme,
+ darkTheme: configStore.amoledDarkMode ? amoledTheme : darkTheme,
+ locale: configStore.locale,
+ theme: lightTheme,
+ home: const HomePage(),
+ ),
+ );
+ }
+}
diff --git a/lib/app_config.dart b/lib/app_config.dart
new file mode 100644
index 0000000..2a86cd5
--- /dev/null
+++ b/lib/app_config.dart
@@ -0,0 +1,7 @@
+class AppConfig {
+ final bool debugMode;
+
+ const AppConfig({
+ required this.debugMode,
+ });
+}
diff --git a/lib/main_common.dart b/lib/main_common.dart
new file mode 100644
index 0000000..3eec4c4
--- /dev/null
+++ b/lib/main_common.dart
@@ -0,0 +1,57 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:logging/logging.dart';
+import 'package:provider/provider.dart';
+
+import 'app.dart';
+import 'app_config.dart';
+import 'pages/log_console_page/log_console_page_store.dart';
+import 'stores/accounts_store.dart';
+import 'stores/config_store.dart';
+
+Future mainCommon(AppConfig appConfig) async {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ final logConsoleStore = LogConsolePageStore();
+
+ _setupLogger(appConfig, logConsoleStore);
+
+ final configStore = await ConfigStore.load();
+ final accountsStore = await AccountsStore.load();
+
+ runApp(
+ MultiProvider(
+ providers: [
+ ChangeNotifierProvider.value(value: configStore),
+ ChangeNotifierProvider.value(value: accountsStore),
+ Provider.value(value: logConsoleStore),
+ ],
+ child: const MyApp(),
+ ),
+ );
+}
+
+void _setupLogger(AppConfig appConfig, LogConsolePageStore logConsoleStore) {
+ Logger.root.level = Level.ALL;
+
+ Logger.root.onRecord.listen((logRecord) {
+ // ignore: avoid_print
+ print(logRecord);
+ logConsoleStore.addLog(logRecord);
+ });
+
+ final flutterErrorLogger = Logger('FlutterError');
+
+ FlutterError.onError = (details) {
+ if (appConfig.debugMode) {
+ FlutterError.dumpErrorToConsole(details);
+ } else {
+ flutterErrorLogger.warning(
+ details.summary.name,
+ details.exception,
+ details.stack,
+ );
+ }
+ };
+}
diff --git a/lib/main_dev.dart b/lib/main_dev.dart
new file mode 100644
index 0000000..0276d2c
--- /dev/null
+++ b/lib/main_dev.dart
@@ -0,0 +1,6 @@
+import 'app_config.dart';
+import 'main_common.dart';
+
+void main() {
+ mainCommon(const AppConfig(debugMode: true));
+}
diff --git a/lib/main_prod.dart b/lib/main_prod.dart
new file mode 100644
index 0000000..b4a7b6e
--- /dev/null
+++ b/lib/main_prod.dart
@@ -0,0 +1,6 @@
+import 'app_config.dart';
+import 'main_common.dart';
+
+void main() {
+ mainCommon(const AppConfig(debugMode: false));
+}
diff --git a/lib/main.dart b/lib/pages/home_page.dart
similarity index 57%
rename from lib/main.dart
rename to lib/pages/home_page.dart
index 71275cb..79794dd 100644
--- a/lib/main.dart
+++ b/lib/pages/home_page.dart
@@ -1,64 +1,16 @@
-import 'dart:async';
-
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
-import 'package:keyboard_dismisser/keyboard_dismisser.dart';
-import 'package:provider/provider.dart';
-import 'hooks/stores.dart';
-import 'l10n/l10n.dart';
-import 'pages/communities_tab.dart';
-import 'pages/create_post.dart';
-import 'pages/home_tab.dart';
-import 'pages/profile_tab.dart';
-import 'pages/search_tab.dart';
-import 'stores/accounts_store.dart';
-import 'stores/config_store.dart';
-import 'theme.dart';
-import 'util/extensions/brightness.dart';
+import '../util/extensions/brightness.dart';
+import 'communities_tab.dart';
+import 'create_post.dart';
+import 'home_tab.dart';
+import 'profile_tab.dart';
+import 'search_tab.dart';
-Future main() async {
- WidgetsFlutterBinding.ensureInitialized();
-
- final configStore = await ConfigStore.load();
- final accountsStore = await AccountsStore.load();
-
- runApp(
- MultiProvider(
- providers: [
- ChangeNotifierProvider.value(value: configStore),
- ChangeNotifierProvider.value(value: accountsStore),
- ],
- child: const MyApp(),
- ),
- );
-}
-
-class MyApp extends HookWidget {
- const MyApp();
-
- @override
- Widget build(BuildContext context) {
- final configStore = useConfigStore();
-
- return KeyboardDismisser(
- child: MaterialApp(
- title: 'lemmur',
- supportedLocales: L10n.supportedLocales,
- localizationsDelegates: L10n.localizationsDelegates,
- themeMode: configStore.theme,
- darkTheme: configStore.amoledDarkMode ? amoledTheme : darkTheme,
- locale: configStore.locale,
- theme: lightTheme,
- home: const MyHomePage(),
- ),
- );
- }
-}
-
-class MyHomePage extends HookWidget {
- const MyHomePage();
+class HomePage extends HookWidget {
+ const HomePage();
static const List pages = [
HomeTab(),
diff --git a/lib/pages/log_console_page/log_console_page.dart b/lib/pages/log_console_page/log_console_page.dart
new file mode 100644
index 0000000..20e0243
--- /dev/null
+++ b/lib/pages/log_console_page/log_console_page.dart
@@ -0,0 +1,81 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:logging/logging.dart';
+import 'package:provider/provider.dart';
+
+import '../../util/observer_consumers.dart';
+import '../../widgets/bottom_safe.dart';
+import 'log_console_page_store.dart';
+
+class LogConsolePage extends StatelessWidget {
+ const LogConsolePage();
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(title: const Text('Logs')),
+ body: SafeArea(
+ child: ObserverBuilder(
+ builder: (context, store) {
+ final logStrings = store.stringified();
+
+ if (store.logs.isEmpty) {
+ return const Center(
+ child: Text(
+ 'no logs',
+ style: TextStyle(fontStyle: FontStyle.italic),
+ ),
+ );
+ }
+
+ return ListView.separated(
+ padding: const EdgeInsets.all(8)
+ .copyWith(bottom: BottomSafe.fabPadding + 8),
+ itemCount: store.logs.length,
+ itemBuilder: (context, i) => SelectableText(
+ logStrings[i],
+ style: TextStyle(color: store.logs[i].level.color),
+ ),
+ separatorBuilder: (context, i) => const SizedBox(height: 6),
+ );
+ },
+ ),
+ ),
+ floatingActionButton: FloatingActionButton(
+ onPressed: () async {
+ final data = context.read().stringified();
+
+ await Clipboard.setData(ClipboardData(text: data.join('\n')));
+
+ ScaffoldMessenger.of(context)
+ ..hideCurrentSnackBar()
+ ..showSnackBar(
+ const SnackBar(content: Text('all logs copied to the clipboard')),
+ );
+ },
+ tooltip: 'Copy to clipboard',
+ child: const Icon(Icons.copy),
+ ),
+ );
+ }
+
+ static Route route() => MaterialPageRoute(
+ builder: (context) => const LogConsolePage(),
+ fullscreenDialog: true,
+ );
+}
+
+extension on Level {
+ Color get color {
+ if (this == Level.FINEST) return Colors.lime[100]!;
+ if (this == Level.FINER) return Colors.lime[300]!;
+ if (this == Level.FINE) return Colors.lime;
+ if (this == Level.CONFIG) return Colors.green;
+ if (this == Level.INFO) return Colors.blue;
+ if (this == Level.WARNING) return Colors.amber;
+ if (this == Level.SEVERE) return Colors.orange;
+ if (this == Level.SHOUT) return Colors.red;
+
+ throw StateError('unreachable');
+ }
+}
diff --git a/lib/pages/log_console_page/log_console_page_store.dart b/lib/pages/log_console_page/log_console_page_store.dart
new file mode 100644
index 0000000..b39cf27
--- /dev/null
+++ b/lib/pages/log_console_page/log_console_page_store.dart
@@ -0,0 +1,33 @@
+import 'package:logging/logging.dart';
+import 'package:mobx/mobx.dart';
+
+part 'log_console_page_store.g.dart';
+
+class LogConsolePageStore = _LogConsolePageStore with _$LogConsolePageStore;
+
+abstract class _LogConsolePageStore with Store {
+ // TODO: implement as an ObservableDeque
+ final logs = ObservableList();
+ static const _bufferSize = 200;
+
+ @action
+ void addLog(LogRecord logRecord) {
+ if (logs.length == _bufferSize) {
+ logs.removeAt(0);
+ }
+
+ logs.add(logRecord);
+ }
+
+ List stringified() {
+ return logs.map(
+ (log) {
+ var str = '${log.time} $log';
+
+ if (log.stackTrace != null) str += '\n${log.stackTrace}';
+
+ return str;
+ },
+ ).toList();
+ }
+}
diff --git a/lib/pages/log_console_page/log_console_page_store.g.dart b/lib/pages/log_console_page/log_console_page_store.g.dart
new file mode 100644
index 0000000..c7fc5e7
--- /dev/null
+++ b/lib/pages/log_console_page/log_console_page_store.g.dart
@@ -0,0 +1,32 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'log_console_page_store.dart';
+
+// **************************************************************************
+// StoreGenerator
+// **************************************************************************
+
+// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
+
+mixin _$LogConsolePageStore on _LogConsolePageStore, Store {
+ final _$_LogConsolePageStoreActionController =
+ ActionController(name: '_LogConsolePageStore');
+
+ @override
+ void addLog(LogRecord logRecord) {
+ final _$actionInfo = _$_LogConsolePageStoreActionController.startAction(
+ name: '_LogConsolePageStore.addLog');
+ try {
+ return super.addLog(logRecord);
+ } finally {
+ _$_LogConsolePageStoreActionController.endAction(_$actionInfo);
+ }
+ }
+
+ @override
+ String toString() {
+ return '''
+
+ ''';
+ }
+}
diff --git a/lib/pages/log_console_page/log_console_store.g.dart b/lib/pages/log_console_page/log_console_store.g.dart
new file mode 100644
index 0000000..621b8c0
--- /dev/null
+++ b/lib/pages/log_console_page/log_console_store.g.dart
@@ -0,0 +1,32 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'log_console_store.dart';
+
+// **************************************************************************
+// StoreGenerator
+// **************************************************************************
+
+// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
+
+mixin _$LogConsoleStore on _LogConsoleStore, Store {
+ final _$_LogConsoleStoreActionController =
+ ActionController(name: '_LogConsoleStore');
+
+ @override
+ void addLog(LogRecord logRecord) {
+ final _$actionInfo = _$_LogConsoleStoreActionController.startAction(
+ name: '_LogConsoleStore.addLog');
+ try {
+ return super.addLog(logRecord);
+ } finally {
+ _$_LogConsoleStoreActionController.endAction(_$actionInfo);
+ }
+ }
+
+ @override
+ String toString() {
+ return '''
+
+ ''';
+ }
+}
diff --git a/lib/url_launcher.dart b/lib/url_launcher.dart
index 75bda92..dc14fe9 100644
--- a/lib/url_launcher.dart
+++ b/lib/url_launcher.dart
@@ -63,7 +63,6 @@ Future linkLauncher({
return goToPost(context, matchedInstance, int.parse(split[2]));
} else if (split.length == 5) {
// TODO: post with focus on comment thread
- print('comment in post');
return goToPost(context, matchedInstance, int.parse(split[2]));
}
break;
@@ -73,21 +72,17 @@ Future linkLauncher({
case 'communities':
// TODO: put here push to communities page
- print('communities');
return;
case 'modlog':
// TODO: put here push to modlog
- print('modlog');
return;
case 'inbox':
// TODO: put here push to inbox
- print('inbox');
return;
case 'search':
// TODO: *maybe* put here push to search. we'll see
// how much web version differs form the app
- print('search');
return;
case 'create_post':
case 'create_community':
diff --git a/lib/widgets/about_tile.dart b/lib/widgets/about_tile.dart
index 7b817ca..bfa0964 100644
--- a/lib/widgets/about_tile.dart
+++ b/lib/widgets/about_tile.dart
@@ -6,6 +6,7 @@ import 'package:package_info_plus/package_info_plus.dart';
import '../gen/assets.gen.dart';
import '../hooks/memo_future.dart';
+import '../pages/log_console_page/log_console_page.dart';
import '../url_launcher.dart';
import 'bottom_modal.dart';
@@ -72,7 +73,14 @@ class AboutTile extends HookWidget {
),
),
);
- }, // TODO: link to some donation site
+ },
+ ),
+ TextButton.icon(
+ icon: const Icon(Icons.list_alt),
+ label: const Text('logs'),
+ onPressed: () {
+ Navigator.of(context).push(LogConsolePage.route());
+ },
),
],
applicationIcon: ClipRRect(
diff --git a/lib/widgets/bottom_safe.dart b/lib/widgets/bottom_safe.dart
index f4eead8..451f8fe 100644
--- a/lib/widgets/bottom_safe.dart
+++ b/lib/widgets/bottom_safe.dart
@@ -2,9 +2,18 @@ import 'package:flutter/material.dart';
class BottomSafe extends StatelessWidget {
final double additionalPadding;
+
+ static const fabPadding =
+ // FAB size + FAB margin, 56 is as per https://material.io/components/buttons-floating-action-button#anatomy
+ 56 + kFloatingActionButtonMargin;
+
const BottomSafe([this.additionalPadding = 0]);
+ const BottomSafe.fab() : this(fabPadding);
@override
- Widget build(BuildContext context) => SizedBox(
- height: MediaQuery.of(context).padding.bottom + additionalPadding);
+ Widget build(BuildContext context) {
+ return SizedBox(
+ height: MediaQuery.of(context).padding.bottom + additionalPadding,
+ );
+ }
}
diff --git a/lib/widgets/comment_section.dart b/lib/widgets/comment_section.dart
index 1e87ff3..b505c9c 100644
--- a/lib/widgets/comment_section.dart
+++ b/lib/widgets/comment_section.dart
@@ -105,7 +105,7 @@ class CommentSection extends HookWidget {
com,
key: ValueKey(com),
),
- const BottomSafe(kMinInteractiveDimension + kFloatingActionButtonMargin),
+ const BottomSafe.fab(),
]);
}
}
diff --git a/lib/widgets/post_list_options.dart b/lib/widgets/post_list_options.dart
index a2bc022..cd9dd8e 100644
--- a/lib/widgets/post_list_options.dart
+++ b/lib/widgets/post_list_options.dart
@@ -31,10 +31,10 @@ class PostListOptions extends StatelessWidget {
),
const Spacer(),
if (styleButton)
- IconButton(
- icon: const Icon(Icons.view_stream),
+ const IconButton(
+ icon: Icon(Icons.view_stream),
// TODO: create compact post and dropdown for selecting
- onPressed: () => print('TBD'),
+ onPressed: null,
),
],
),
diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt
index a1da1b9..6dc9705 100644
--- a/linux/flutter/CMakeLists.txt
+++ b/linux/flutter/CMakeLists.txt
@@ -82,7 +82,7 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
- linux-x64 ${CMAKE_BUILD_TYPE}
+ ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
diff --git a/pubspec.lock b/pubspec.lock
index 87d82b6..604d757 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -456,7 +456,7 @@ packages:
source: hosted
version: "0.16.0"
logging:
- dependency: transitive
+ dependency: "direct main"
description:
name: logging
url: "https://pub.dartlang.org"
diff --git a/pubspec.yaml b/pubspec.yaml
index ba60ea2..35357a5 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -55,6 +55,7 @@ dependencies:
json_annotation: ^4.1.0
keyboard_dismisser: ^2.0.0
freezed_annotation: ^0.14.3
+ logging: ^1.0.1
flutter:
sdk: flutter
diff --git a/scripts/common.dart b/scripts/common.dart
index c718091..a9e820d 100644
--- a/scripts/common.dart
+++ b/scripts/common.dart
@@ -1,3 +1,4 @@
+// ignore_for_file: avoid_print
import 'dart:io';
void confirm(String message) {
diff --git a/scripts/release.dart b/scripts/release.dart
index aa5d393..5771c9e 100755
--- a/scripts/release.dart
+++ b/scripts/release.dart
@@ -1,3 +1,4 @@
+// ignore_for_file: avoid_print
/// Used to create a new lemmur release. Bumps semver, build number, updates changelog, fastlane, pubspec, and finishes by adding a git tag
import 'dart:io';