Merge pull request #2 from timokz/refactor/flatten-layout
Refactor/flatten layout
46
README.md
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Wien Talks
|
||||||
|
|
||||||
|
**An app developed during the [2nd Flutter Vienna Hackathon](https://www.meetup.com/fluttervienna/events/310014357/).**
|
||||||
|
|
||||||
|
## Team
|
||||||
|
|
||||||
|
_Grätzel-Goblins_ — Timo, Max & Mike
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
_Wien Talks_ is an app to share the iconic and quotable lines you hear Viennese
|
||||||
|
people drop throughout their day. It doesn't matter if it's a U6 train
|
||||||
|
conductor's insults that he made during an announcement or the Spar cashier's
|
||||||
|
funny retort to "Zweite Kassa bitte!", _Wien Talks_ is made to record, preserve
|
||||||
|
and share Viennese quotes with others.
|
||||||
|
|
||||||
|
Quotes are community-moderated—users can up- or down-vote posts. Additionally,
|
||||||
|
when creating a new quote, the location from which the user made it is added,
|
||||||
|
too. This allows you to see what's going on near you.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<img src="unicorn/Screenshot_1755421875.png" width="300" alt="Default quotes list"/>
|
||||||
|
<div><sub>Default quotes view</sub></div>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<img src="unicorn/Screenshot_1755421892.png" width="300" alt="Filtered quotes list"/>
|
||||||
|
<div><sub>Filtered quotes</sub></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<img src="unicorn/Screenshot_1755421897.png" width="300" alt="Reuse location from quote"/>
|
||||||
|
<div><sub>Tap a card → create with same location</sub></div>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<img src="unicorn/Screenshot_1755421907.png" width="300" alt="Custom location picker"/>
|
||||||
|
<div><sub>Set a custom location</sub></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
---
|
||||||
BIN
unicorn/Screenshot_1755421875.png
Normal file
|
After Width: | Height: | Size: 1,011 KiB |
BIN
unicorn/Screenshot_1755421892.png
Normal file
|
After Width: | Height: | Size: 930 KiB |
BIN
unicorn/Screenshot_1755421897.png
Normal file
|
After Width: | Height: | Size: 570 KiB |
BIN
unicorn/Screenshot_1755421907.png
Normal file
|
After Width: | Height: | Size: 566 KiB |
75
wien_talks/.github/workflows/deployment-aws.yml
vendored
|
|
@ -1,75 +0,0 @@
|
||||||
name: Deploy to AWS
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ deployment-aws-production, deployment-aws-staging ]
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
target:
|
|
||||||
description: 'Target'
|
|
||||||
required: true
|
|
||||||
default: 'production'
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- 'staging'
|
|
||||||
- 'production'
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
name: Deploy to AWS
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Setup Dart SDK
|
|
||||||
uses: dart-lang/setup-dart@v1.6.5
|
|
||||||
with:
|
|
||||||
sdk: 3.5
|
|
||||||
|
|
||||||
- name: Configure AWS credentials
|
|
||||||
uses: aws-actions/configure-aws-credentials@v1
|
|
||||||
with:
|
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
aws-region: us-west-2
|
|
||||||
|
|
||||||
- name: Create passwords file
|
|
||||||
working-directory: wien_talks_server
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
SERVERPOD_PASSWORDS: ${{ secrets.SERVERPOD_PASSWORDS }}
|
|
||||||
run: |
|
|
||||||
pwd
|
|
||||||
echo "$SERVERPOD_PASSWORDS" > config/passwords.yaml
|
|
||||||
ls config/
|
|
||||||
|
|
||||||
- name: Get Dart packages
|
|
||||||
working-directory: wien_talks_server
|
|
||||||
run: dart pub get
|
|
||||||
|
|
||||||
- name: Compile server
|
|
||||||
working-directory: wien_talks_server
|
|
||||||
run: dart compile kernel bin/main.dart
|
|
||||||
|
|
||||||
- name: Create CodeDeploy Deployment
|
|
||||||
id: deploy
|
|
||||||
env:
|
|
||||||
PROJECT_NAME: wien_talks
|
|
||||||
AWS_NAME: wien-talks
|
|
||||||
DEPLOYMENT_BUCKET: wien-talks-deployment-6559518
|
|
||||||
TARGET: ${{ github.event.inputs.target }}
|
|
||||||
run: |
|
|
||||||
# Deploy server to AWS
|
|
||||||
TARGET="${TARGET:=${GITHUB_REF##*-}}"
|
|
||||||
echo "Deploying to target: $TARGET"
|
|
||||||
mkdir -p vendor
|
|
||||||
cp "${PROJECT_NAME}_server/deploy/aws/scripts/appspec.yml" appspec.yml
|
|
||||||
zip -r deployment.zip .
|
|
||||||
aws s3 cp deployment.zip "s3://${DEPLOYMENT_BUCKET}/deployment.zip"
|
|
||||||
aws deploy create-deployment \
|
|
||||||
--application-name "${AWS_NAME}-app" \
|
|
||||||
--deployment-group-name "${AWS_NAME}-${TARGET}-group" \
|
|
||||||
--deployment-config-name CodeDeployDefault.OneAtATime \
|
|
||||||
--s3-location "bucket=${DEPLOYMENT_BUCKET},key=deployment.zip,bundleType=zip"
|
|
||||||
99
wien_talks/.github/workflows/deployment-gcp.yml
vendored
|
|
@ -1,99 +0,0 @@
|
||||||
name: Deploy to GCP
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ deployment-gcp-production, deployment-gcp-staging ]
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
target:
|
|
||||||
description: 'Target'
|
|
||||||
required: true
|
|
||||||
default: 'production'
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- 'staging'
|
|
||||||
- 'production'
|
|
||||||
|
|
||||||
env:
|
|
||||||
# TODO: Update with your Google Cloud project id. If you have changed the
|
|
||||||
# region and zone in your Terraform configuration, you will need to change
|
|
||||||
# it here too.
|
|
||||||
PROJECT: "<PROJECT ID>"
|
|
||||||
REGION: us-central1
|
|
||||||
ZONE: us-central1-c
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
name: Deploy to Google Cloud Run
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Setting Target Mode from Input
|
|
||||||
if: ${{ github.event.inputs.target != '' }}
|
|
||||||
run: echo "TARGET=${{ github.event.inputs.target }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Setting Target mode based on branch
|
|
||||||
if: ${{ github.event.inputs.target == '' }}
|
|
||||||
run: echo "TARGET=${GITHUB_REF##*-}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Set repository
|
|
||||||
run: echo "REPOSITORY=serverpod-${{ env.TARGET }}-container" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Set Image Name
|
|
||||||
run: echo "IMAGE_NAME=serverpod" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Set Service Name
|
|
||||||
run: echo "SERVICE_NAME=$(echo $IMAGE_NAME | sed 's/[^a-zA-Z0-9]/-/g')" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: echo $SERVICE_NAME
|
|
||||||
|
|
||||||
|
|
||||||
- id: "auth"
|
|
||||||
name: "Authenticate to Google Cloud"
|
|
||||||
uses: "google-github-actions/auth@v1"
|
|
||||||
with:
|
|
||||||
credentials_json: "${{ secrets.GOOGLE_CREDENTIALS }}"
|
|
||||||
|
|
||||||
- name: Create passwords file
|
|
||||||
working-directory: wien_talks_server
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
SERVERPOD_PASSWORDS: ${{ secrets.SERVERPOD_PASSWORDS }}
|
|
||||||
run: |
|
|
||||||
pwd
|
|
||||||
echo "$SERVERPOD_PASSWORDS" > config/passwords.yaml
|
|
||||||
ls config/
|
|
||||||
|
|
||||||
- name: Configure Docker
|
|
||||||
working-directory: wien_talks_server
|
|
||||||
run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev
|
|
||||||
|
|
||||||
- name: Build the Docker image
|
|
||||||
working-directory: wien_talks_server
|
|
||||||
run: "docker build -t $IMAGE_NAME ."
|
|
||||||
|
|
||||||
- name: Tag the Docker image
|
|
||||||
working-directory: wien_talks_server
|
|
||||||
run: docker tag $IMAGE_NAME ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT }}/${{ env.REPOSITORY }}/$IMAGE_NAME
|
|
||||||
|
|
||||||
- name: Push Docker image
|
|
||||||
working-directory: wien_talks_server
|
|
||||||
run: docker push ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT }}/${{ env.REPOSITORY }}/$IMAGE_NAME
|
|
||||||
|
|
||||||
# Uncomment the following code to automatically restart the servers in the
|
|
||||||
# instance group when you push a new version of your code. Before doing
|
|
||||||
# this, make sure that you have successfully deployed a first version.
|
|
||||||
#
|
|
||||||
# - name: Restart servers in instance group
|
|
||||||
# run: |
|
|
||||||
# gcloud compute instance-groups managed rolling-action replace serverpod-${{ env.TARGET }}-group \
|
|
||||||
# --project=${{ env.PROJECT }} \
|
|
||||||
# --replacement-method='substitute' \
|
|
||||||
# --max-surge=1 \
|
|
||||||
# --max-unavailable=1 \
|
|
||||||
# --zone=${{ env.ZONE }}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
plugins {
|
|
||||||
id("com.android.application")
|
|
||||||
id("kotlin-android")
|
|
||||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "com.wien_talks"
|
|
||||||
compileSdk = flutter.compileSdkVersion
|
|
||||||
ndkVersion = flutter.ndkVersion
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId = "com.wien_talks"
|
|
||||||
// You can update the following values to match your application needs.
|
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
|
||||||
minSdk = flutter.minSdkVersion
|
|
||||||
targetSdk = flutter.targetSdkVersion
|
|
||||||
versionCode = flutter.versionCode
|
|
||||||
versionName = flutter.versionName
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
// TODO: Add your own signing config for the release build.
|
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
flutter {
|
|
||||||
source = "../.."
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:wien_talks_flutter/screens/create_event_screen.dart';
|
|
||||||
import 'package:wien_talks_flutter/screens/login_page.dart';
|
|
||||||
import 'package:wien_talks_flutter/screens/news_screen.dart';
|
|
||||||
|
|
||||||
final router = GoRouter(
|
|
||||||
routes: [
|
|
||||||
GoRoute(path: '/login', builder: (c, s) => const LoginScreen()),
|
|
||||||
GoRoute(path: '/', builder: (c, s) => NewsScreen()),
|
|
||||||
GoRoute(
|
|
||||||
path: '/create_event',
|
|
||||||
name: 'create_event',
|
|
||||||
builder: (c, s) => CreateEventScreen()),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:location/location.dart';
|
|
||||||
import 'package:wien_talks_client/wien_talks_client.dart';
|
|
||||||
import 'package:wien_talks_flutter/widgets/get_location_widget.dart';
|
|
||||||
import 'package:wien_talks_flutter/helper/funmap_mgr.dart';
|
|
||||||
import 'package:wien_talks_flutter/mapfile_widget.dart';
|
|
||||||
import 'package:wien_talks_flutter/widgets/news_input_form.dart';
|
|
||||||
import 'package:wien_talks_flutter/widgets/screen_widget.dart';
|
|
||||||
|
|
||||||
import '../helper/location_mgr.dart';
|
|
||||||
|
|
||||||
class CreateEventScreen extends StatelessWidget {
|
|
||||||
const CreateEventScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ScreenWidget(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
NewsInputForm(
|
|
||||||
onSubmit: (CreateQuoteRequest request) async {
|
|
||||||
await FunmapMgr().client.quote.createQuote(request);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
StreamBuilder(
|
|
||||||
stream: LocationMgr().stream,
|
|
||||||
builder:
|
|
||||||
(BuildContext context, AsyncSnapshot<LocationData> snapshot) =>
|
|
||||||
snapshot.data != null
|
|
||||||
? Text(snapshot.data.toString())
|
|
||||||
: SizedBox()),
|
|
||||||
Expanded(
|
|
||||||
child: GetLocationWidget(
|
|
||||||
child: MapfileWidget(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
|
||||||
import 'package:wien_talks_flutter/helper/auth_service.dart';
|
|
||||||
|
|
||||||
class LoginScreen extends StatelessWidget {
|
|
||||||
const LoginScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
body: Container(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [Color(0xff2193b0), Color(0xff6dd5ed)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text('Wien Talks',
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
fontSize: 42,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white)),
|
|
||||||
const SizedBox(height: 60),
|
|
||||||
FilledButton.icon(
|
|
||||||
onPressed: () async => await AuthService.signIn(),
|
|
||||||
style: FilledButton.styleFrom(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
foregroundColor: Colors.black87,
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(30)),
|
|
||||||
elevation: 6,
|
|
||||||
),
|
|
||||||
icon: Icon(
|
|
||||||
Icons.lock,
|
|
||||||
),
|
|
||||||
label: const Text('Sign in with Google'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
|
||||||
import 'package:wien_talks_client/wien_talks_client.dart';
|
|
||||||
import 'package:wien_talks_flutter/helper/funmap_mgr.dart';
|
|
||||||
import 'package:wien_talks_flutter/helper/location_util.dart';
|
|
||||||
import 'package:wien_talks_flutter/helper/time_util.dart';
|
|
||||||
import 'package:wien_talks_flutter/widgets/flamboyant_quote_card.dart';
|
|
||||||
|
|
||||||
class LatestQuotesScreen extends StatefulWidget {
|
|
||||||
const LatestQuotesScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<LatestQuotesScreen> createState() => _LatestQuotesScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LatestQuotesScreenState extends State<LatestQuotesScreen> {
|
|
||||||
final List<Quote> _quotes = [];
|
|
||||||
StreamSubscription<Quote>? _sub;
|
|
||||||
|
|
||||||
Object? _error;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_connectStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_sub?.cancel();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _connectStream() {
|
|
||||||
_sub?.cancel();
|
|
||||||
_sub = FunmapMgr().client.quote.streamAllQuotes(limit: 50).listen(
|
|
||||||
(q) => setState(() => _upsert(q)),
|
|
||||||
onError: (e) => setState(() => _error = e),
|
|
||||||
onDone: () => Future.delayed(const Duration(seconds: 2), () {
|
|
||||||
if (mounted) _connectStream();
|
|
||||||
}),
|
|
||||||
cancelOnError: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _upsert(Quote q) {
|
|
||||||
final i = _quotes.indexWhere((x) => x.id == q.id);
|
|
||||||
if (i >= 0) {
|
|
||||||
_quotes[i] = q;
|
|
||||||
} else {
|
|
||||||
_quotes.add(q);
|
|
||||||
}
|
|
||||||
_quotes.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _sortDesc() {
|
|
||||||
_quotes.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _vote(Quote quote, bool up) async {
|
|
||||||
final idx = _quotes.indexWhere((q) => q.id == quote.id);
|
|
||||||
if (idx < 0) return;
|
|
||||||
|
|
||||||
final original = _quotes[idx];
|
|
||||||
final updated = original.copyWith(
|
|
||||||
upvotes: up ? original.upvotes + 1 : original.upvotes,
|
|
||||||
downvotes: up ? original.downvotes : original.downvotes + 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_quotes[idx] = updated;
|
|
||||||
_sortDesc();
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await FunmapMgr().client.quote.updateQuote(updated);
|
|
||||||
} catch (e) {
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() => _quotes[idx] = original);
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Vote failed: $e')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (_quotes.isEmpty && _error == null) {
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
if (_error != null && _quotes.isEmpty) {
|
|
||||||
return Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Text('Error: $_error'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (_quotes.isEmpty) {
|
|
||||||
return const Center(child: Text('Nix da. Sag halt was'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return MasonryGridView.count(
|
|
||||||
crossAxisCount: 2,
|
|
||||||
mainAxisSpacing: 8,
|
|
||||||
crossAxisSpacing: 8,
|
|
||||||
padding: const EdgeInsets.fromLTRB(12, 8, 12, 12),
|
|
||||||
itemCount: _quotes.length,
|
|
||||||
itemBuilder: (context, i) {
|
|
||||||
final q = _quotes[i];
|
|
||||||
final author = (q.authorName ?? '').trim();
|
|
||||||
final meta = [
|
|
||||||
if (author.isNotEmpty) author,
|
|
||||||
timeAgo(q.createdAt),
|
|
||||||
].join(' · ');
|
|
||||||
|
|
||||||
return FlamboyantQuoteCard(
|
|
||||||
quote: q,
|
|
||||||
meta: meta,
|
|
||||||
onVoteUp: () => _vote(q, true),
|
|
||||||
onVoteDown: () => _vote(q, false),
|
|
||||||
staticMapUrlBuilder: gStaticMap);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import 'package:carousel_slider/carousel_slider.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class CarouselWidget extends StatelessWidget {
|
|
||||||
const CarouselWidget({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return IgnorePointer(
|
|
||||||
child: CarouselSlider(
|
|
||||||
options: CarouselOptions(height: 300.0, autoPlay: true),
|
|
||||||
items: ["houses.jpg", "kangaroos.jpg", "sightseeing.jpg", "tram.jpg", "fiaker.jpg", "falco.jpg", "wastebin.jpg"].map((i) {
|
|
||||||
return Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
margin: EdgeInsets.symmetric(horizontal: 5.0),
|
|
||||||
//decoration: BoxDecoration(color: Colors.amber),
|
|
||||||
child: Image(image: AssetImage("assets/funny_images/$i")));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
wien_talks/wien_talks_client/.idea/.gitignore → wien_talks_client/.idea/.gitignore
generated
vendored
|
|
@ -44,6 +44,7 @@ app.*.map.json
|
||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
android/key.properties
|
||||||
|
|
||||||
|
|
||||||
.env
|
.env
|
||||||
72
wien_talks_flutter/android/app/build.gradle.kts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import java.util.Properties
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
id("kotlin-android")
|
||||||
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("com.google.android.play:core:1.10.3")
|
||||||
|
}
|
||||||
|
|
||||||
|
val keystoreProperties = Properties()
|
||||||
|
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
FileInputStream(keystorePropertiesFile).use { keystoreProperties.load(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.wien_talks"
|
||||||
|
compileSdk = flutter.compileSdkVersion
|
||||||
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
kotlinOptions { jvmTarget = "17" }
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
create("release") {
|
||||||
|
keyAlias = keystoreProperties["keyAlias"] as String
|
||||||
|
keyPassword = keystoreProperties["keyPassword"] as String
|
||||||
|
val storePath = keystoreProperties["storeFile"] as String?
|
||||||
|
storeFile = storePath?.let { file(it) }
|
||||||
|
storePassword = keystoreProperties["storePassword"] as String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.wien_talks"
|
||||||
|
minSdk = flutter.minSdkVersion
|
||||||
|
targetSdk = flutter.targetSdkVersion
|
||||||
|
versionCode = flutter.versionCode
|
||||||
|
versionName = flutter.versionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
signingConfig = signingConfigs.findByName("release")
|
||||||
|
?: signingConfigs.getByName("debug")
|
||||||
|
|
||||||
|
isMinifyEnabled = true
|
||||||
|
isShrinkResources = true
|
||||||
|
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
||||||
8
wien_talks_flutter/android/app/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
-keep class io.flutter.embedding.** { *; }
|
||||||
|
-keep class io.flutter.plugins.** { *; }
|
||||||
|
-keep class io.flutter.** { *; }
|
||||||
|
|
||||||
|
-keep class com.google.android.gms.** { *; }
|
||||||
|
-dontwarn com.google.android.gms.**
|
||||||
|
|
||||||
|
-dontwarn kotlinx.coroutines.**
|
||||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 450 B |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 282 B |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 462 B |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 704 B |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 586 B |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 762 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 68 B |