11 Commits
mvp2 ... mvp5

62 changed files with 210 additions and 83 deletions

View File

@@ -37,7 +37,7 @@ jobs:
run: flutter build linux run: flutter build linux
- name: Build for Android - name: Build for Android
run: flutter build appbundle run: flutter build appbundle --debug
- name: Build for web - name: Build for web
run: flutter build web --no-web-resources-cdn --csp --web-renderer canvaskit run: flutter build web --no-web-resources-cdn --csp --web-renderer canvaskit
@@ -62,3 +62,13 @@ jobs:
name: flutter-web-build-latest name: flutter-web-build-latest
path: build/web path: build/web
if-no-files-found: error if-no-files-found: error
- name: Publish to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_PAGES_DEPLOY }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: ything-radio
directory: build/web
# push directly to "production" on cloudflare pages
branch: main

View File

@@ -16,7 +16,7 @@ This app has been designed to run across all platforms!
- MacOS - MacOS
- Windows - Windows
- Linux - Linux
- Web - Web ([Live Demo](https://radio-demo.ything.app/))
Provide a single unified experience across *all* platforms for your users! Provide a single unified experience across *all* platforms for your users!
@@ -27,5 +27,4 @@ Coming Soon:
- Apple App Store Live Demo! - Apple App Store Live Demo!
- Google Play Store Live Demo! - Google Play Store Live Demo!
- Windows portable exe - Windows portable exe
- Web based live demo
- Screenshots / Demo Videos - Screenshots / Demo Videos

View File

@@ -5,6 +5,12 @@ plugins {
id "dev.flutter.flutter-gradle-plugin" id "dev.flutter.flutter-gradle-plugin"
} }
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android { android {
namespace = "net.ything.radio.android" namespace = "net.ything.radio.android"
//compileSdk = flutter.compileSdkVersion //compileSdk = flutter.compileSdkVersion
@@ -31,11 +37,21 @@ android {
versionName = flutter.versionName versionName = flutter.versionName
} }
signingConfigs {
release {
keyAlias = keystoreProperties['keyAlias']
keyPassword = keystoreProperties['keyPassword']
storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword = keystoreProperties['storePassword']
}
}
buildTypes { buildTypes {
release { release {
// TODO: Add your own signing config for the release build. // TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works. // Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.debug signingConfig = signingConfigs.release
} }
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,4 @@
storePassword=
keyPassword=
keyAlias=
storeFile=

View File

@@ -0,0 +1,4 @@
https://docs.flutter.dev/deployment/android#signing-the-app
keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

1
assets/appicon/radio.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-radio"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 3l-9.371 3.749a1 1 0 0 0 -.629 .928v11.323a1 1 0 0 0 1 1h14a1 1 0 0 0 1 -1v-11a1 1 0 0 0 -1 -1h-14.5" /><path d="M4 12h16" /><path d="M7 12v-2" /><path d="M17 16v.01" /><path d="M13 16v.01" /></svg>

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

View File

@@ -1,6 +1,7 @@
flutter_launcher_icons: flutter_launcher_icons:
android: true android: true
ios: true ios: true
remove_alpha_ios: true
image_path: "assets/appicon/icon.png" image_path: "assets/appicon/icon.png"
min_sdk_android: 21 min_sdk_android: 21
adaptive_icon_background: "#FFA86A" adaptive_icon_background: "#FFA86A"

View File

@@ -40,5 +40,12 @@ end
post_install do |installer| post_install do |installer|
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target) flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'AUDIO_SESSION_MICROPHONE=0'
]
end
end end
end end

View File

@@ -49,6 +49,6 @@ SPEC CHECKSUMS:
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 PODFILE CHECKSUM: fe2316eacdf4337bc10b81b9cf460c6f69aee374
COCOAPODS: 1.15.2 COCOAPODS: 1.15.2

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -6,35 +6,44 @@ class About extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeZone( return Container(
child: DefaultTextStyle( decoration: const BoxDecoration(
style: Theme.of(context).textTheme.bodyLarge!, image: DecorationImage(
child: const SingleChildScrollView( image: AssetImage('assets/images/app-background2.jpg'),
child: Text( fit: BoxFit.cover,
'Ything Radio is an app that is intended to provide a demonstration of the ' opacity: 0.2,
'open source ything_radio app. This app is used to provide the ability for the ' ),
'"look and feel" of the app to be tested on real devices for those wishing to ' ),
'either use the code as a template for themselves; or, alternatively, have Ything LLC ' child: SafeZone(
'create a custom app for them!' child: DefaultTextStyle(
'\n\n' style: Theme.of(context).textTheme.bodyLarge!,
'This app intentionally does not provide a method to change the streaming source ' child: const SingleChildScrollView(
'as it is intended for use by internet radio stations to provide a dedicated app ' child: Text(
'for their radio station. This is not intended to be an app for generic usage for ' 'Ything Radio is an app that is intended to provide a demonstration of the '
'multiple radio stations, a discovery platform, or to facilitate user input for ' 'open source ything_radio app. This app is used to provide the ability for the '
'internet radio sites.' '"look and feel" of the app to be tested on real devices for those wishing to '
'\n\n' 'either use the code as a template for themselves; or, alternatively, have Ything LLC '
'This app has been designed to run across all platforms!' 'create a custom app for them!'
'\n\n' '\n\n'
'Android, iOS, MacOS, Windows, Linux, and Web!' 'This app intentionally does not provide a method to change the streaming source '
'\n\n' 'as it is intended for use by internet radio stations to provide a dedicated app '
'Have a single unified experience across all platforms for your users!' 'for their radio station. This is not intended to be an app for generic usage for '
'\n\n' 'multiple radio stations, a discovery platform, or to facilitate user input for '
'Want to have us modify this app for you? Reach out at info@ything.net ' 'internet radio sites.'
'We can add additional features for you! Schedule views, up next listings, ' '\n\n'
'and even custom user interactivity! ' 'This app has been designed to run across all platforms!'
'\n\n' '\n\n'
'We look forward to hearing from you soon! We would love to build your app!', 'Android, iOS, MacOS, Windows, Linux, and Web!'
textAlign: TextAlign.justify, '\n\n'
'Have a single unified experience across all platforms for your users!'
'\n\n'
'Want to have us modify this app for you? Reach out at info@ything.net '
'We can add additional features for you! Schedule views, up next listings, '
'and even custom user interactivity! '
'\n\n'
'We look forward to hearing from you soon! We would love to build your app!',
textAlign: TextAlign.justify,
),
), ),
), ),
), ),

View File

@@ -17,6 +17,36 @@ Future<void> setupListenHandler() async {
_session = await AudioSession.instance; _session = await AudioSession.instance;
await _session.configure(const AudioSessionConfiguration.music()); await _session.configure(const AudioSessionConfiguration.music());
//TODO: Do I need to handle these events? audioplayers may be doing it already
//_session.interruptionEventStream.listen((event) {
// if (event.begin) {
// switch (event.type) {
// case AudioInterruptionType.duck:
// _listenHandler.duck();
// break;
// case AudioInterruptionType.pause:
// case AudioInterruptionType.unknown:
// _listenHandler.pause();
// break;
// }
// } else {
// switch (event.type) {
// case AudioInterruptionType.duck:
// _listenHandler.unDuck();
// break;
// case AudioInterruptionType.pause:
// _listenHandler.play();
// break;
// case AudioInterruptionType.unknown:
// break;
// }
// }
//});
//_session.becomingNoisyEventStream.listen((_) {
// _listenHandler.pause();
//});
} }
ListenHandler getListenHandlder() => _listenHandler; ListenHandler getListenHandlder() => _listenHandler;

View File

@@ -38,12 +38,21 @@ class Links extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DefaultTextStyle( return Container(
style: Theme.of(context).textTheme.headlineSmall!, decoration: const BoxDecoration(
child: SafeZone( image: DecorationImage(
child: Center( image: AssetImage('assets/images/app-background3.jpg'),
child: Column( fit: BoxFit.cover,
children: links.map((link) => link).toList(), opacity: 0.4,
),
),
child: DefaultTextStyle(
style: Theme.of(context).textTheme.headlineSmall!,
child: SafeZone(
child: Center(
child: Column(
children: links.map((link) => link).toList(),
),
), ),
), ),
), ),

View File

@@ -9,8 +9,17 @@ class Listen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const Center( return Container(
child: PlayControls(), decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/app-background.jpg'),
fit: BoxFit.cover,
opacity: 0.4,
),
),
child: const Center(
child: PlayControls(),
),
); );
} }
} }
@@ -27,8 +36,6 @@ class _PlayControlsState extends State<PlayControls>
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
bool isPlaying = false;
final ListenHandler _listenHandler = getListenHandlder(); final ListenHandler _listenHandler = getListenHandlder();
@override @override
@@ -37,15 +44,9 @@ class _PlayControlsState extends State<PlayControls>
print("Listen init"); print("Listen init");
_listenHandler.playbackState.listen((PlaybackState event) { _listenHandler.playbackState.listen((PlaybackState state) {
if (isPlaying == event.playing) { print("Playback state changed");
print("State unchanged, skipping setState()"); setState(() {});
return;
}
setState(() {
isPlaying = event.playing;
print("Updated playing state to $isPlaying");
});
}); });
} }
@@ -56,48 +57,33 @@ class _PlayControlsState extends State<PlayControls>
_listenHandler.stop(); _listenHandler.stop();
} }
Future<void> playPause() async {
print("playPause called");
_listenHandler.isPlaying()
? await _listenHandler.stop()
: await _listenHandler.play();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
print("Running build - Listen"); print("Running build - Listen");
if (isPlaying) {
_listenHandler.play();
_listenHandler.playbackState.add(PlaybackState(
controls: [
MediaControl.stop,
MediaControl.pause,
],
systemActions: {
MediaAction.stop,
MediaAction.pause,
},
playing: true,
));
} else {
_listenHandler.stop();
_listenHandler.playbackState.add(PlaybackState(
controls: [],
systemActions: {},
playing: false,
));
}
return ClipOval( return ClipOval(
child: Material( child: Material(
color: Theme.of(context).colorScheme.primaryContainer, color: Theme.of(context).colorScheme.primaryContainer,
child: InkWell( child: InkWell(
splashColor: Theme.of(context).colorScheme.onPrimaryContainer, splashColor: Theme.of(context).colorScheme.onPrimaryContainer,
onTap: () { onTap: () async {
setState(() { await playPause();
isPlaying = !isPlaying; setState(() {});
});
}, },
child: SizedBox( child: SizedBox(
width: 100, width: 100,
height: 100, height: 100,
child: Icon( child: Icon(
isPlaying ? Icons.stop : Icons.play_arrow, _listenHandler.isPlaying() ? Icons.stop : Icons.play_arrow,
color: Colors.white, color: Colors.white,
size: 50, size: 50,
), ),

View File

@@ -11,12 +11,59 @@ class ListenHandler extends BaseAudioHandler {
setup_player() { setup_player() {
_player.setReleaseMode(ReleaseMode.release); _player.setReleaseMode(ReleaseMode.release);
_player.onPlayerStateChanged.listen((event) {
print("Received audioplayers event: $event");
switch (event) {
case PlayerState.playing:
super.playbackState.add(PlaybackState(
controls: [
MediaControl.stop,
MediaControl.pause,
],
systemActions: {
MediaAction.stop,
MediaAction.pause,
},
playing: true,
));
break;
case PlayerState.paused:
case PlayerState.stopped:
case PlayerState.completed:
case PlayerState.disposed:
super.playbackState.add(PlaybackState(
controls: [],
systemActions: {},
playing: false,
));
break;
}
});
} }
ListenHandler() { ListenHandler() {
setup_player(); setup_player();
} }
bool isPlaying() => super.playbackState.value.playing;
double _duckVol = 0;
void duck() {
//TODO: Confirm I need to do this manually, it looks like audioplayers already does this
print("Duck requested");
//_duckVol = _player.volume;
//_player.setVolume(_duckVol - 0.4 > 0 ? _duckVol - 0.4 : 0.0);
}
void unDuck() {
//TODO: Confirm I need to do this manually, it looks like audioplayers already does this
print("Unduck requested");
//_player.setVolume(_duckVol);
}
@override @override
Future<void> play() async { Future<void> play() async {
if (await startAudioSession()) { if (await startAudioSession()) {

View File

@@ -10,6 +10,7 @@ class YthingRadio extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: 'Ything Radio', title: 'Ything Radio',
debugShowCheckedModeBanner: false,
theme: ThemeData( theme: ThemeData(
colorScheme: ColorScheme.fromSeed( colorScheme: ColorScheme.fromSeed(
seedColor: Colors.orange, seedColor: Colors.orange,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 881 B

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1 version: 1.0.1+a
environment: environment:
sdk: ^3.5.1 sdk: ^3.5.1
@@ -66,9 +66,12 @@ flutter:
uses-material-design: true uses-material-design: true
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
# assets: assets:
# - images/a_dot_burr.jpeg # - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg # - images/a_dot_ham.jpeg
- assets/images/app-background.jpg
- assets/images/app-background2.jpg
- assets/images/app-background3.jpg
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images # https://flutter.dev/to/resolution-aware-images

Binary file not shown.

Before

Width:  |  Height:  |  Size: 881 B

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB