2024-06-25 12:00:34 +02:00
import ' package:flutter/material.dart ' ;
import ' package:font_awesome_flutter/font_awesome_flutter.dart ' ;
import ' package:qr_flutter/qr_flutter.dart ' ;
import ' package:share_plus/share_plus.dart ' ;
import ' package:simplecloudnotifier/api/api_client.dart ' ;
import ' package:simplecloudnotifier/components/layout/scaffold.dart ' ;
import ' package:simplecloudnotifier/models/channel.dart ' ;
2024-10-19 22:33:08 +02:00
import ' package:simplecloudnotifier/models/scan_result.dart ' ;
2024-06-25 12:00:34 +02:00
import ' package:simplecloudnotifier/models/subscription.dart ' ;
import ' package:simplecloudnotifier/models/user.dart ' ;
2024-07-13 00:11:13 +02:00
import ' package:simplecloudnotifier/pages/channel_message_view/channel_message_view.dart ' ;
2024-06-25 12:00:34 +02:00
import ' package:simplecloudnotifier/state/app_auth.dart ' ;
import ' package:simplecloudnotifier/state/app_bar_state.dart ' ;
2024-06-26 14:54:34 +02:00
import ' package:simplecloudnotifier/state/application_log.dart ' ;
2024-06-25 20:49:40 +02:00
import ' package:simplecloudnotifier/types/immediate_future.dart ' ;
2024-07-13 00:11:13 +02:00
import ' package:simplecloudnotifier/utils/navi.dart ' ;
2024-06-26 14:54:34 +02:00
import ' package:simplecloudnotifier/utils/toaster.dart ' ;
2024-06-25 12:00:34 +02:00
import ' package:simplecloudnotifier/utils/ui.dart ' ;
2024-06-25 20:49:40 +02:00
import ' package:provider/provider.dart ' ;
2024-06-25 12:00:34 +02:00
class ChannelViewPage extends StatefulWidget {
const ChannelViewPage ( {
2024-07-13 00:11:13 +02:00
required this . channelID ,
required this . preloadedData ,
2024-06-26 14:54:34 +02:00
required this . needsReload ,
2024-06-25 12:00:34 +02:00
super . key ,
} ) ;
2024-07-13 00:11:13 +02:00
final String channelID ;
final ( Channel , Subscription ? ) ? preloadedData ;
2024-06-25 12:00:34 +02:00
2024-06-26 14:54:34 +02:00
final void Function ( ) ? needsReload ;
2024-06-25 12:00:34 +02:00
@ override
State < ChannelViewPage > createState ( ) = > _ChannelViewPageState ( ) ;
}
2024-06-26 14:54:34 +02:00
enum EditState { none , editing , saving }
2024-07-13 00:11:13 +02:00
enum ChannelViewPageInitState { loading , okay , error }
2024-06-25 12:00:34 +02:00
class _ChannelViewPageState extends State < ChannelViewPage > {
2024-06-25 20:49:40 +02:00
late ImmediateFuture < String ? > _futureSubscribeKey ;
2024-07-12 23:08:56 +02:00
late ImmediateFuture < List < ( Subscription , UserPreview ? ) > > _futureSubscriptions ;
2024-06-25 20:49:40 +02:00
late ImmediateFuture < UserPreview > _futureOwner ;
2024-06-26 14:54:34 +02:00
final TextEditingController _ctrlDisplayName = TextEditingController ( ) ;
final TextEditingController _ctrlDescriptionName = TextEditingController ( ) ;
2024-06-25 20:49:40 +02:00
int _loadingIndeterminateCounter = 0 ;
2024-06-25 12:00:34 +02:00
2024-06-26 14:54:34 +02:00
EditState _editDisplayName = EditState . none ;
String ? _displayNameOverride = null ;
EditState _editDescriptionName = EditState . none ;
String ? _descriptionNameOverride = null ;
2024-07-13 00:11:13 +02:00
ChannelPreview ? channelPreview ;
Channel ? channel ;
Subscription ? subscription ;
ChannelViewPageInitState loadingState = ChannelViewPageInitState . loading ;
String errorMessage = ' ' ;
2024-06-25 12:00:34 +02:00
@ override
void initState ( ) {
2024-10-19 19:42:05 +02:00
_initStateAsync ( true ) ;
2024-07-13 00:11:13 +02:00
super . initState ( ) ;
}
2024-10-19 19:42:05 +02:00
Future < void > _initStateAsync ( bool usePreload ) async {
2024-06-25 20:49:40 +02:00
final userAcc = Provider . of < AppAuth > ( context , listen: false ) ;
2024-10-19 19:42:05 +02:00
if ( widget . preloadedData ! = null & & usePreload ) {
2024-07-13 00:11:13 +02:00
channelPreview = widget . preloadedData ! . $1 . toPreview ( ) ;
channel = widget . preloadedData ! . $1 ;
subscription = widget . preloadedData ! . $2 ;
} else {
try {
var p = await APIClient . getChannelPreview ( userAcc , widget . channelID ) ;
channelPreview = p ;
if ( p . ownerUserID = = userAcc . userID ) {
var r = await APIClient . getChannel ( userAcc , widget . channelID ) ;
channel = r . channel ;
subscription = r . subscription ;
} else {
channel = null ;
subscription = null ; //TODO get own subscription on this channel, even though its foreign channel
}
} catch ( exc , trace ) {
ApplicationLog . error ( ' Failed to load data: ' + exc . toString ( ) , trace: trace ) ;
Toaster . error ( " Error " , ' Failed to load data ' ) ;
this . errorMessage = ' Failed to load data: ' + exc . toString ( ) ;
this . loadingState = ChannelViewPageInitState . error ;
return ;
}
}
this . loadingState = ChannelViewPageInitState . okay ;
assert ( channelPreview ! = null ) ;
if ( this . channelPreview ! . ownerUserID = = userAcc . userID ) {
if ( this . channel ! = null & & this . channel ! . subscribeKey ! = null ) {
_futureSubscribeKey = ImmediateFuture < String ? > . ofValue ( this . channel ! . subscribeKey ) ;
2024-06-25 20:49:40 +02:00
} else {
2024-07-13 00:11:13 +02:00
_futureSubscribeKey = ImmediateFuture < String ? > . ofFuture ( _getSubscribeKey ( userAcc ) ) ;
2024-06-25 20:49:40 +02:00
}
2024-07-12 23:08:56 +02:00
_futureSubscriptions = ImmediateFuture < List < ( Subscription , UserPreview ? ) > > . ofFuture ( _listSubscriptions ( userAcc ) ) ;
2024-06-25 20:49:40 +02:00
} else {
_futureSubscribeKey = ImmediateFuture < String ? > . ofValue ( null ) ;
2024-07-12 23:08:56 +02:00
_futureSubscriptions = ImmediateFuture < List < ( Subscription , UserPreview ? ) > > . ofValue ( [ ] ) ;
2024-06-25 20:49:40 +02:00
}
2024-07-13 00:11:13 +02:00
if ( this . channelPreview ! . ownerUserID = = userAcc . userID ) {
2024-06-25 20:49:40 +02:00
var cacheUser = userAcc . getUserOrNull ( ) ;
if ( cacheUser ! = null ) {
_futureOwner = ImmediateFuture < UserPreview > . ofValue ( cacheUser . toPreview ( ) ) ;
} else {
_futureOwner = ImmediateFuture < UserPreview > . ofFuture ( _getOwner ( userAcc ) ) ;
}
} else {
2024-07-13 00:11:13 +02:00
_futureOwner = ImmediateFuture < UserPreview > . ofFuture ( APIClient . getUserPreview ( userAcc , this . channelPreview ! . ownerUserID ) ) ;
2024-06-25 20:49:40 +02:00
}
2024-06-25 12:00:34 +02:00
}
@ override
void dispose ( ) {
2024-06-26 14:54:34 +02:00
_ctrlDisplayName . dispose ( ) ;
_ctrlDescriptionName . dispose ( ) ;
2024-06-25 12:00:34 +02:00
super . dispose ( ) ;
}
@ override
Widget build ( BuildContext context ) {
2024-07-13 00:11:13 +02:00
final userAcc = Provider . of < AppAuth > ( context , listen: false ) ;
Widget child ;
if ( loadingState = = ChannelViewPageInitState . loading ) {
child = Center ( child: CircularProgressIndicator ( ) ) ;
} else if ( loadingState = = ChannelViewPageInitState . error ) {
child = Center ( child: Text ( ' Error: ' + errorMessage ) ) ; //TODO better error
} else if ( loadingState = = ChannelViewPageInitState . okay & & channelPreview ! . ownerUserID = = userAcc . userID ) {
child = _buildOwnedChannelView ( context , this . channel ! ) ;
} else {
child = _buildForeignChannelView ( context , this . channelPreview ! ) ;
}
2024-06-25 12:00:34 +02:00
return SCNScaffold (
title: ' Channel ' ,
showSearch: false ,
showShare: false ,
2024-07-13 00:11:13 +02:00
child: child ,
2024-06-25 12:00:34 +02:00
) ;
}
2024-07-13 00:11:13 +02:00
Widget _buildOwnedChannelView ( BuildContext context , Channel channel ) {
final isSubscribed = ( subscription ! = null & & subscription ! . confirmed ) ;
2024-06-25 20:49:40 +02:00
2024-06-25 12:00:34 +02:00
return SingleChildScrollView (
child: Padding (
padding: const EdgeInsets . fromLTRB ( 24 , 16 , 24 , 16 ) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . stretch ,
children: [
_buildQRCode ( context ) ,
SizedBox ( height: 8 ) ,
2024-06-25 20:49:40 +02:00
UI . metaCard (
context: context ,
icon: FontAwesomeIcons . solidIdCardClip ,
title: ' ChannelID ' ,
2024-07-13 00:11:13 +02:00
values: [ channel . channelID ] ,
2024-06-25 20:49:40 +02:00
) ,
UI . metaCard (
context: context ,
icon: FontAwesomeIcons . solidInputNumeric ,
title: ' InternalName ' ,
2024-07-13 00:11:13 +02:00
values: [ channel . internalName ] ,
2024-06-25 20:49:40 +02:00
) ,
2024-07-13 00:11:13 +02:00
_buildDisplayNameCard ( context , true ) ,
_buildDescriptionNameCard ( context , true ) ,
2024-06-25 20:49:40 +02:00
UI . metaCard (
context: context ,
icon: FontAwesomeIcons . solidDiagramSubtask ,
title: ' Subscription (own) ' ,
2024-07-13 00:11:13 +02:00
values: [ _formatSubscriptionStatus ( this . subscription ) ] ,
2024-06-25 20:49:40 +02:00
iconActions: isSubscribed ? [ ( FontAwesomeIcons . solidSquareXmark , _unsubscribe ) ] : [ ( FontAwesomeIcons . solidSquareRss , _subscribe ) ] ,
) ,
_buildForeignSubscriptions ( context ) ,
2024-07-13 00:11:13 +02:00
_buildOwnerCard ( context , true ) ,
2024-06-25 20:49:40 +02:00
UI . metaCard (
context: context ,
icon: FontAwesomeIcons . solidEnvelope ,
title: ' Messages ' ,
2024-07-13 00:11:13 +02:00
values: [ channel . messagesSent . toString ( ) ] ,
mainAction: ( ) {
Navi . push ( context , ( ) = > ChannelMessageViewPage ( channel: channel ) ) ;
} ,
) ,
] ,
) ,
) ,
) ;
}
Widget _buildForeignChannelView ( BuildContext context , ChannelPreview channel ) {
final isSubscribed = ( subscription ! = null & & subscription ! . confirmed ) ;
return SingleChildScrollView (
child: Padding (
padding: const EdgeInsets . fromLTRB ( 24 , 16 , 24 , 16 ) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . stretch ,
children: [
SizedBox ( height: 8 ) ,
UI . metaCard (
context: context ,
icon: FontAwesomeIcons . solidIdCardClip ,
title: ' ChannelID ' ,
values: [ channel . channelID ] ,
) ,
UI . metaCard (
context: context ,
icon: FontAwesomeIcons . solidInputNumeric ,
title: ' InternalName ' ,
values: [ channel . internalName ] ,
2024-06-25 20:49:40 +02:00
) ,
2024-07-13 00:11:13 +02:00
_buildDisplayNameCard ( context , false ) ,
_buildDescriptionNameCard ( context , false ) ,
UI . metaCard (
context: context ,
icon: FontAwesomeIcons . solidDiagramSubtask ,
2024-10-19 19:42:05 +02:00
title: ' Subscription (foreign) ' ,
2024-07-13 00:11:13 +02:00
values: [ _formatSubscriptionStatus ( subscription ) ] ,
iconActions: isSubscribed ? [ ( FontAwesomeIcons . solidSquareXmark , _unsubscribe ) ] : [ ( FontAwesomeIcons . solidSquareRss , _subscribe ) ] ,
) ,
_buildForeignSubscriptions ( context ) ,
_buildOwnerCard ( context , false ) ,
2024-06-25 12:00:34 +02:00
] ,
) ,
) ,
) ;
}
2024-06-25 20:49:40 +02:00
Widget _buildForeignSubscriptions ( BuildContext context ) {
return FutureBuilder (
future: _futureSubscriptions . future ,
builder: ( context , snapshot ) {
if ( snapshot . hasData ) {
return Column (
crossAxisAlignment: CrossAxisAlignment . stretch ,
2024-06-25 12:00:34 +02:00
children: [
2024-07-13 00:11:13 +02:00
for ( final ( sub , user ) in snapshot . data ! . where ( ( v ) = > v . $1 . subscriptionID ! = subscription ? . subscriptionID ) )
2024-06-25 20:49:40 +02:00
UI . metaCard (
context: context ,
icon: FontAwesomeIcons . solidDiagramSuccessor ,
2024-07-12 23:08:56 +02:00
title: ' Subscription ( ' + ( user ? . username ? ? user ? . userID ? ? ' other ' ) + ' ) ' ,
2024-06-25 20:49:40 +02:00
values: [ _formatSubscriptionStatus ( sub ) ] ,
2024-07-12 23:08:56 +02:00
iconActions: _getForeignSubActions ( sub ) ,
2024-06-25 20:49:40 +02:00
) ,
2024-06-25 12:00:34 +02:00
] ,
2024-06-25 20:49:40 +02:00
) ;
} else {
return SizedBox ( ) ;
}
} ,
2024-06-25 12:00:34 +02:00
) ;
2024-06-25 20:49:40 +02:00
}
Widget _buildOwnerCard ( BuildContext context , bool isOwned ) {
return FutureBuilder (
future: _futureOwner . future ,
builder: ( context , snapshot ) {
if ( snapshot . hasData ) {
return UI . metaCard (
context: context ,
icon: FontAwesomeIcons . solidUser ,
title: ' Owner ' ,
2024-07-13 00:11:13 +02:00
values: [ channelPreview ! . ownerUserID + ( isOwned ? ' (you) ' : ' ' ) , if ( snapshot . data ? . username ! = null ) snapshot . data ! . username ! ] ,
2024-06-25 20:49:40 +02:00
) ;
} else {
return UI . metaCard (
context: context ,
icon: FontAwesomeIcons . solidUser ,
title: ' Owner ' ,
2024-07-13 00:11:13 +02:00
values: [ channelPreview ! . ownerUserID + ( isOwned ? ' (you) ' : ' ' ) ] ,
2024-06-25 20:49:40 +02:00
) ;
}
} ,
) ;
}
Widget _buildQRCode ( BuildContext context ) {
return FutureBuilder (
future: _futureSubscribeKey . future ,
builder: ( context , snapshot ) {
2024-10-19 22:33:08 +02:00
if ( snapshot . hasData ) {
final text = ( snapshot . data = = null ) ? ScanResult . createChannelQR ( channel ! ) : ScanResult . createChannelSubscribeQR ( channel ! , snapshot . data ! ) ;
2024-06-25 20:49:40 +02:00
return GestureDetector (
onTap: ( ) {
2024-07-13 00:11:13 +02:00
Share . share ( text , subject: _displayNameOverride ? ? channel ! . displayName ) ;
2024-06-25 20:49:40 +02:00
} ,
child: Center (
child: QrImageView (
data: text ,
version: QrVersions . auto ,
2024-10-19 19:42:05 +02:00
size: 265.0 ,
2024-06-25 20:49:40 +02:00
eyeStyle: QrEyeStyle (
eyeShape: QrEyeShape . square ,
color: Theme . of ( context ) . textTheme . bodyLarge ? . color ,
) ,
dataModuleStyle: QrDataModuleStyle (
dataModuleShape: QrDataModuleShape . square ,
color: Theme . of ( context ) . textTheme . bodyLarge ? . color ,
) ,
) ,
) ,
) ;
} else {
return const SizedBox (
width: 300.0 ,
height: 300.0 ,
child: Center ( child: CircularProgressIndicator ( ) ) ,
) ;
}
} ,
) ;
}
2024-06-26 14:54:34 +02:00
Widget _buildDisplayNameCard ( BuildContext context , bool isOwned ) {
if ( _editDisplayName = = EditState . editing ) {
return Padding (
padding: EdgeInsets . symmetric ( vertical: 4 , horizontal: 0 ) ,
child: UI . box (
context: context ,
padding: EdgeInsets . fromLTRB ( 16 , 2 , 4 , 2 ) ,
child: Row (
children: [
Container ( child: Center ( child: FaIcon ( FontAwesomeIcons . solidInputText , size: 18 ) ) , height: 43 ) ,
SizedBox ( width: 16 ) ,
Expanded (
child: TextField (
autofocus: true ,
controller: _ctrlDisplayName ,
decoration: new InputDecoration . collapsed ( hintText: ' DisplayName ' ) ,
) ,
) ,
SizedBox ( width: 12 ) ,
SizedBox ( width: 4 ) ,
IconButton ( icon: FaIcon ( FontAwesomeIcons . solidFloppyDisk ) , onPressed: _saveDisplayName ) ,
] ,
) ,
) ,
) ;
} else if ( _editDisplayName = = EditState . none ) {
return UI . metaCard (
context: context ,
icon: FontAwesomeIcons . solidInputText ,
title: ' DisplayName ' ,
2024-07-13 00:11:13 +02:00
values: [ _displayNameOverride ? ? channelPreview ! . displayName ] ,
2024-06-26 14:54:34 +02:00
iconActions: isOwned ? [ ( FontAwesomeIcons . penToSquare , _showEditDisplayName ) ] : [ ] ,
) ;
} else if ( _editDisplayName = = EditState . saving ) {
return Padding (
padding: EdgeInsets . symmetric ( vertical: 4 , horizontal: 0 ) ,
child: UI . box (
context: context ,
padding: EdgeInsets . fromLTRB ( 16 , 2 , 4 , 2 ) ,
child: Row (
children: [
Container ( child: Center ( child: FaIcon ( FontAwesomeIcons . solidInputText , size: 18 ) ) , height: 43 ) ,
SizedBox ( width: 16 ) ,
Expanded ( child: SizedBox ( ) ) ,
SizedBox ( width: 12 ) ,
SizedBox ( width: 4 ) ,
Padding ( padding: const EdgeInsets . all ( 8.0 ) , child: SizedBox ( width: 18 , height: 18 , child: CircularProgressIndicator ( ) ) ) ,
] ,
) ,
) ,
) ;
} else {
throw ' Invalid EditDisplayNameState: $ _editDisplayName ' ;
}
}
Widget _buildDescriptionNameCard ( BuildContext context , bool isOwned ) {
if ( _editDescriptionName = = EditState . editing ) {
return Padding (
padding: EdgeInsets . symmetric ( vertical: 4 , horizontal: 0 ) ,
child: UI . box (
context: context ,
padding: EdgeInsets . fromLTRB ( 16 , 2 , 4 , 2 ) ,
child: Row (
children: [
Container ( child: Center ( child: FaIcon ( FontAwesomeIcons . solidInputPipe , size: 18 ) ) , height: 43 ) ,
SizedBox ( width: 16 ) ,
Expanded (
child: TextField (
autofocus: true ,
controller: _ctrlDescriptionName ,
decoration: new InputDecoration . collapsed ( hintText: ' Description ' ) ,
) ,
) ,
SizedBox ( width: 12 ) ,
SizedBox ( width: 4 ) ,
IconButton ( icon: FaIcon ( FontAwesomeIcons . solidFloppyDisk ) , onPressed: _saveDescriptionName ) ,
] ,
) ,
) ,
) ;
} else if ( _editDescriptionName = = EditState . none ) {
return UI . metaCard (
context: context ,
icon: FontAwesomeIcons . solidInputPipe ,
title: ' Description ' ,
2024-07-13 00:11:13 +02:00
values: [ _descriptionNameOverride ? ? channelPreview ? . descriptionName ? ? ' ' ] ,
2024-06-26 14:54:34 +02:00
iconActions: isOwned ? [ ( FontAwesomeIcons . penToSquare , _showEditDescriptionName ) ] : [ ] ,
) ;
} else if ( _editDescriptionName = = EditState . saving ) {
return Padding (
padding: EdgeInsets . symmetric ( vertical: 4 , horizontal: 0 ) ,
child: UI . box (
context: context ,
padding: EdgeInsets . fromLTRB ( 16 , 2 , 4 , 2 ) ,
child: Row (
children: [
Container ( child: Center ( child: FaIcon ( FontAwesomeIcons . solidInputPipe , size: 18 ) ) , height: 43 ) ,
SizedBox ( width: 16 ) ,
Expanded ( child: SizedBox ( ) ) ,
SizedBox ( width: 12 ) ,
SizedBox ( width: 4 ) ,
Padding ( padding: const EdgeInsets . all ( 8.0 ) , child: SizedBox ( width: 18 , height: 18 , child: CircularProgressIndicator ( ) ) ) ,
] ,
) ,
) ,
) ;
} else {
throw ' Invalid EditDescriptionNameState: $ _editDescriptionName ' ;
}
2024-06-25 20:49:40 +02:00
}
2024-06-26 14:54:34 +02:00
void _showEditDisplayName ( ) {
setState ( ( ) {
2024-07-13 00:11:13 +02:00
_ctrlDisplayName . text = _displayNameOverride ? ? channelPreview ? . displayName ? ? ' ' ;
2024-06-26 14:54:34 +02:00
_editDisplayName = EditState . editing ;
if ( _editDescriptionName = = EditState . editing ) _editDescriptionName = EditState . none ;
} ) ;
}
void _saveDisplayName ( ) async {
final userAcc = Provider . of < AppAuth > ( context , listen: false ) ;
final newName = _ctrlDisplayName . text ;
try {
setState ( ( ) {
_editDisplayName = EditState . saving ;
} ) ;
2024-07-13 00:11:13 +02:00
final newChannel = await APIClient . updateChannel ( userAcc , widget . channelID , displayName: newName ) ;
2024-06-26 14:54:34 +02:00
setState ( ( ) {
_editDisplayName = EditState . none ;
_displayNameOverride = newChannel . channel . displayName ;
} ) ;
widget . needsReload ? . call ( ) ;
} catch ( exc , trace ) {
ApplicationLog . error ( ' Failed to save DisplayName: ' + exc . toString ( ) , trace: trace ) ;
Toaster . error ( " Error " , ' Failed to save DisplayName ' ) ;
}
}
void _showEditDescriptionName ( ) {
setState ( ( ) {
2024-07-13 00:11:13 +02:00
_ctrlDescriptionName . text = _descriptionNameOverride ? ? channelPreview ? . descriptionName ? ? ' ' ;
2024-06-26 14:54:34 +02:00
_editDescriptionName = EditState . editing ;
if ( _editDisplayName = = EditState . editing ) _editDisplayName = EditState . none ;
} ) ;
}
void _saveDescriptionName ( ) async {
final userAcc = Provider . of < AppAuth > ( context , listen: false ) ;
final newName = _ctrlDescriptionName . text ;
try {
setState ( ( ) {
_editDescriptionName = EditState . saving ;
} ) ;
2024-07-13 00:11:13 +02:00
final newChannel = await APIClient . updateChannel ( userAcc , widget . channelID , descriptionName: newName ) ;
2024-06-26 14:54:34 +02:00
setState ( ( ) {
_editDescriptionName = EditState . none ;
_descriptionNameOverride = newChannel . channel . descriptionName ? ? ' ' ;
} ) ;
widget . needsReload ? . call ( ) ;
} catch ( exc , trace ) {
ApplicationLog . error ( ' Failed to save DescriptionName: ' + exc . toString ( ) , trace: trace ) ;
Toaster . error ( " Error " , ' Failed to save DescriptionName ' ) ;
}
}
2024-10-19 19:42:05 +02:00
void _subscribe ( ) async {
final acc = AppAuth ( ) ;
try {
var sub = await APIClient . subscribeToChannelbyID ( acc , widget . channelID ) ;
widget . needsReload ? . call ( ) ;
await _initStateAsync ( false ) ;
if ( sub . confirmed ) {
Toaster . success ( " Success " , ' Subscribed to channel ' ) ;
} else {
Toaster . success ( " Success " , ' Requested subscription to channel ' ) ;
}
} catch ( exc , trace ) {
Toaster . error ( " Error " , ' Failed to subscribe to channel ' ) ;
ApplicationLog . error ( ' Failed to subscribe to channel: ' + exc . toString ( ) , trace: trace ) ;
}
2024-06-25 20:49:40 +02:00
}
2024-10-19 19:42:05 +02:00
void _unsubscribe ( ) async {
final acc = AppAuth ( ) ;
if ( subscription = = null ) return ;
try {
await APIClient . deleteSubscription ( acc , widget . channelID , subscription ! . subscriptionID ) ;
widget . needsReload ? . call ( ) ;
await _initStateAsync ( false ) ;
Toaster . success ( " Success " , ' Unsubscribed from channel ' ) ;
} catch ( exc , trace ) {
Toaster . error ( " Error " , ' Failed to unsubscribe from channel ' ) ;
ApplicationLog . error ( ' Failed to unsubscribe from channel: ' + exc . toString ( ) , trace: trace ) ;
}
2024-06-25 20:49:40 +02:00
}
2024-10-19 19:42:05 +02:00
void _cancelForeignSubscription ( Subscription sub ) async {
final acc = AppAuth ( ) ;
try {
await APIClient . unconfirmSubscription ( acc , widget . channelID , subscription ! . subscriptionID ) ;
widget . needsReload ? . call ( ) ;
await _initStateAsync ( false ) ;
Toaster . success ( " Success " , ' Subscription succesfully revoked ' ) ;
} catch ( exc , trace ) {
Toaster . error ( " Error " , ' Failed to revoke subscription ' ) ;
ApplicationLog . error ( ' Failed to revoke subscription: ' + exc . toString ( ) , trace: trace ) ;
}
}
void _confirmForeignSubscription ( Subscription sub ) async {
final acc = AppAuth ( ) ;
try {
await APIClient . confirmSubscription ( acc , widget . channelID , subscription ! . subscriptionID ) ;
widget . needsReload ? . call ( ) ;
await _initStateAsync ( false ) ;
Toaster . success ( " Success " , ' Subscription succesfully confirmed ' ) ;
} catch ( exc , trace ) {
Toaster . error ( " Error " , ' Failed to confirm subscription ' ) ;
ApplicationLog . error ( ' Failed to confirm subscription: ' + exc . toString ( ) , trace: trace ) ;
}
}
void _denyForeignSubscription ( Subscription sub ) async {
final acc = AppAuth ( ) ;
try {
await APIClient . deleteSubscription ( acc , widget . channelID , subscription ! . subscriptionID ) ;
widget . needsReload ? . call ( ) ;
await _initStateAsync ( false ) ;
Toaster . success ( " Success " , ' Subscription request succesfully denied ' ) ;
} catch ( exc , trace ) {
Toaster . error ( " Error " , ' Failed to deny subscription ' ) ;
ApplicationLog . error ( ' Failed to deny subscription: ' + exc . toString ( ) , trace: trace ) ;
}
2024-06-25 20:49:40 +02:00
}
String _formatSubscriptionStatus ( Subscription ? subscription ) {
if ( subscription = = null ) {
return ' Not Subscribed ' ;
} else if ( subscription . confirmed ) {
return ' Subscribed ' ;
2024-06-25 12:00:34 +02:00
} else {
2024-06-25 20:49:40 +02:00
return ' Requested ' ;
}
}
2024-07-13 00:11:13 +02:00
Future < String ? > _getSubscribeKey ( AppAuth auth ) async {
2024-06-25 20:49:40 +02:00
try {
await Future . delayed ( const Duration ( seconds: 0 ) , ( ) { } ) ; // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception....
_incLoadingIndeterminateCounter ( 1 ) ;
2024-07-13 00:11:13 +02:00
var channel = await APIClient . getChannel ( auth , widget . channelID ) ;
2024-06-25 20:49:40 +02:00
//await Future.delayed(const Duration(seconds: 10), () {});
return channel . channel . subscribeKey ;
} finally {
_incLoadingIndeterminateCounter ( - 1 ) ;
}
}
2024-07-12 23:08:56 +02:00
Future < List < ( Subscription , UserPreview ? ) > > _listSubscriptions ( AppAuth auth ) async {
2024-06-25 20:49:40 +02:00
try {
await Future . delayed ( const Duration ( seconds: 0 ) , ( ) { } ) ; // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception....
_incLoadingIndeterminateCounter ( 1 ) ;
2024-07-13 00:11:13 +02:00
var subs = await APIClient . getChannelSubscriptions ( auth , widget . channelID ) ;
2024-06-25 20:49:40 +02:00
2024-07-12 23:08:56 +02:00
var userMap = { for ( var v in ( await Future . wait ( subs . map ( ( e ) = > e . subscriberUserID ) . toSet ( ) . map ( ( e ) = > APIClient . getUserPreview ( auth , e ) ) . toList ( ) ) ) ) v . userID: v } ;
2024-06-25 20:49:40 +02:00
//await Future.delayed(const Duration(seconds: 10), () {});
2024-07-12 23:08:56 +02:00
return subs . map ( ( e ) = > ( e , userMap [ e . subscriberUserID ] ? ? null ) ) . toList ( ) ;
2024-06-25 20:49:40 +02:00
} finally {
_incLoadingIndeterminateCounter ( - 1 ) ;
2024-06-25 12:00:34 +02:00
}
}
2024-06-25 20:49:40 +02:00
Future < UserPreview > _getOwner ( AppAuth auth ) async {
try {
await Future . delayed ( const Duration ( seconds: 0 ) , ( ) { } ) ; // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception....
_incLoadingIndeterminateCounter ( 1 ) ;
2024-07-13 00:11:13 +02:00
final owner = APIClient . getUserPreview ( auth , channelPreview ! . ownerUserID ) ;
2024-06-25 20:49:40 +02:00
//await Future.delayed(const Duration(seconds: 10), () {});
return owner ;
} finally {
_incLoadingIndeterminateCounter ( - 1 ) ;
}
2024-06-25 12:00:34 +02:00
}
2024-07-12 23:08:56 +02:00
List < ( IconData , void Function ( ) ) > _getForeignSubActions ( Subscription sub ) {
2024-06-25 20:49:40 +02:00
if ( sub . confirmed ) {
return [ ( FontAwesomeIcons . solidSquareXmark , ( ) = > _cancelForeignSubscription ( sub ) ) ] ;
} else {
return [
( FontAwesomeIcons . solidSquareCheck , ( ) = > _confirmForeignSubscription ( sub ) ) ,
( FontAwesomeIcons . solidSquareXmark , ( ) = > _denyForeignSubscription ( sub ) ) ,
] ;
2024-06-25 12:00:34 +02:00
}
}
2024-06-25 20:49:40 +02:00
void _incLoadingIndeterminateCounter ( int delta ) {
setState ( ( ) {
_loadingIndeterminateCounter + = delta ;
AppBarState ( ) . setLoadingIndeterminate ( _loadingIndeterminateCounter > 0 ) ;
} ) ;
2024-06-25 12:00:34 +02:00
}
}